37 using namespace std::string_literals;
42 tx[sfTransactionType.jsonName].asString();
45 txType == jss::TicketCreate,
46 "Unexpected TransactionType: "s + txType))
50 std::uint32_t const count = {tx[sfTicketCount.jsonName].asUInt()};
56 std::uint32_t const txSeq = {tx[sfSequence.jsonName].asUInt()};
57 std::string const account = tx[sfAccount.jsonName].asString();
61 metadata.
isMember(sfTransactionResult.jsonName) &&
62 metadata[sfTransactionResult.jsonName].
asString() ==
64 "Not metadata for successful TicketCreate."))
67 BEAST_EXPECT(metadata.
isMember(sfAffectedNodes.jsonName));
68 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].
isArray());
70 bool directoryChanged =
false;
74 for (
Json::Value const& node : metadata[sfAffectedNodes.jsonName])
76 if (node.isMember(sfModifiedNode.jsonName))
78 Json::Value const& modified = node[sfModifiedNode.jsonName];
80 modified[sfLedgerEntryType.jsonName].
asString();
81 if (entryType == jss::AccountRoot)
83 auto const& previousFields =
84 modified[sfPreviousFields.jsonName];
85 auto const& finalFields = modified[sfFinalFields.jsonName];
89 previousFields[sfSequence.jsonName].asUInt();
92 finalFields[sfSequence.jsonName].asUInt();
97 BEAST_EXPECT(acctRootFinalSeq == prevSeq + count);
102 BEAST_EXPECT(prevSeq == txSeq);
104 acctRootFinalSeq == prevSeq + count + 1);
109 txSeq == 0u ? 1u : 0u};
118 bool const unreportedPrevTicketCount = {
119 count == 1 && txSeq == 0};
122 if (unreportedPrevTicketCount)
127 !previousFields.isMember(sfOwnerCount.jsonName));
133 previousFields[sfOwnerCount.jsonName].asUInt()};
136 finalFields[sfOwnerCount.jsonName].asUInt()};
139 prevCount + count - consumedTickets == finalCount);
143 BEAST_EXPECT(finalFields.isMember(sfTicketCount.jsonName));
145 if (unreportedPrevTicketCount)
150 !previousFields.isMember(sfTicketCount.jsonName));
157 previousFields.isMember(sfTicketCount.jsonName)
158 ? previousFields[sfTicketCount.jsonName]
164 previousFields.isMember(sfTicketCount.jsonName));
167 startCount + count - consumedTickets ==
168 finalFields[sfTicketCount.jsonName]);
171 else if (entryType == jss::DirectoryNode)
173 directoryChanged =
true;
178 "Unexpected modified node: "s + entryType,
183 else if (node.isMember(sfCreatedNode.jsonName))
185 Json::Value const& created = node[sfCreatedNode.jsonName];
187 created[sfLedgerEntryType.jsonName].
asString();
188 if (entryType == jss::Ticket)
190 auto const& newFields = created[sfNewFields.jsonName];
193 newFields[sfAccount.jsonName].asString() == account);
195 newFields[sfTicketSequence.jsonName].asUInt());
197 else if (entryType == jss::DirectoryNode)
199 directoryChanged =
true;
204 "Unexpected created node: "s + entryType,
209 else if (node.isMember(sfDeletedNode.jsonName))
211 Json::Value const& deleted = node[sfDeletedNode.jsonName];
213 deleted[sfLedgerEntryType.jsonName].asString();
215 if (entryType == jss::Ticket)
218 BEAST_EXPECT(txSeq == 0);
221 auto const& finalFields = deleted[sfFinalFields.jsonName];
223 finalFields[sfAccount.jsonName].asString() == account);
227 finalFields[sfTicketSequence.jsonName].asUInt() ==
228 tx[sfTicketSequence.jsonName].asUInt());
234 "Unexpected node type in TicketCreate metadata.",
239 BEAST_EXPECT(directoryChanged);
242 BEAST_EXPECT(ticketSeqs.
size() == count);
247 BEAST_EXPECT(*ticketSeqs.
rbegin() == acctRootFinalSeq - 1);
282 BEAST_EXPECT(tx[sfSequence.jsonName].asUInt() == 0);
283 std::string const account{tx[sfAccount.jsonName].asString()};
285 tx.isMember(sfTicketSequence.jsonName),
286 "Not metadata for a ticket consuming transaction."))
289 std::uint32_t const ticketSeq{tx[sfTicketSequence.jsonName].asUInt()};
293 metadata.isMember(sfTransactionResult.jsonName),
294 "Metadata is missing TransactionResult."))
299 metadata[sfTransactionResult.jsonName].asString()};
301 transactionResult ==
"tesSUCCESS" ||
302 transactionResult.compare(0, 3,
"tec") == 0,
303 transactionResult +
" neither tesSUCCESS nor tec"))
307 BEAST_EXPECT(metadata.isMember(sfAffectedNodes.jsonName));
308 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].isArray());
310 bool acctRootFound{
false};
312 int ticketsRemoved{0};
313 for (
Json::Value const& node : metadata[sfAffectedNodes.jsonName])
315 if (node.isMember(sfModifiedNode.jsonName))
317 Json::Value const& modified{node[sfModifiedNode.jsonName]};
319 modified[sfLedgerEntryType.jsonName].asString();
320 if (entryType ==
"AccountRoot" &&
321 modified[sfFinalFields.jsonName][sfAccount.jsonName]
322 .asString() == account)
324 acctRootFound =
true;
326 auto const& previousFields =
327 modified[sfPreviousFields.jsonName];
328 auto const& finalFields = modified[sfFinalFields.jsonName];
330 acctRootSeq = finalFields[sfSequence.jsonName].asUInt();
336 previousFields.isMember(sfTicketCount.jsonName),
337 "AccountRoot previous is missing TicketCount"))
341 previousFields[sfTicketCount.jsonName].asUInt();
343 BEAST_EXPECT(prevTicketCount > 0);
344 if (prevTicketCount == 1)
346 !finalFields.isMember(sfTicketCount.jsonName));
349 finalFields.isMember(sfTicketCount.jsonName) &&
350 finalFields[sfTicketCount.jsonName].asUInt() ==
351 prevTicketCount - 1);
354 else if (node.isMember(sfDeletedNode.jsonName))
356 Json::Value const& deleted{node[sfDeletedNode.jsonName]};
358 deleted[sfLedgerEntryType.jsonName].asString()};
360 if (entryType == jss::Ticket)
364 deleted[sfFinalFields.jsonName][sfAccount.jsonName]
365 .asString() == account);
369 deleted[sfFinalFields.jsonName]
370 [sfTicketSequence.jsonName]
371 .asUInt() == ticketSeq);
377 BEAST_EXPECT(acctRootFound);
378 BEAST_EXPECT(ticketsRemoved == 1);
379 BEAST_EXPECT(ticketSeq < acctRootSeq);
387 using namespace test::jtx;
388 Env env{*
this, testable_amendments() - featureTicketBatch};
390 env(ticket::create(env.master, 1), ter(
temDISABLED));
392 env.require(owners(env.master, 0), tickets(env.master, 0));
395 env(
noop(env.master),
397 seq(env.seq(env.master)),
402 for (
int i = 0; i < 8; ++i)
405 env.enableFeature(featureTicketBatch);
407 env.require(owners(env.master, 0), tickets(env.master, 0));
410 env(ticket::create(env.master, 2));
413 env.require(owners(env.master, 2), tickets(env.master, 2));
415 env(
noop(env.master), ticket::use(ticketSeq++));
418 env.require(owners(env.master, 1), tickets(env.master, 1));
421 ticket::use(ticketSeq++),
425 env.require(owners(env.master, 0), tickets(env.master, 0));
431 testcase(
"Create Tickets that fail Preflight");
433 using namespace test::jtx;
436 Account
const master{env.master};
444 env(ticket::create(master, 1), fee(XRP(10)));
447 env.require(owners(master, 1), tickets(master, 1));
449 env(ticket::create(master, 1), fee(XRP(-1)), ter(
temBAD_FEE));
456 env.require(owners(master, 2), tickets(master, 2));
460 env.require(owners(master, 2), tickets(master, 2));
464 env(
noop(master), ticket::use(ticketSeq_A));
467 env.require(owners(master, 1), tickets(master, 1));
469 env(ticket::create(master, 250), ticket::use(ticketSeq_B));
472 env.require(owners(master, 250), tickets(master, 250));
478 testcase(
"Create Tickets that fail Preclaim");
480 using namespace test::jtx;
484 Account alice{
"alice"};
487 env(ticket::create(alice, 1),
488 json(jss::Sequence, 1),
495 Account alice{
"alice"};
497 env.fund(XRP(100000), alice);
500 env(ticket::create(alice, 250));
503 env.require(owners(alice, 250), tickets(alice, 250));
507 env(ticket::create(alice, 1), ticket::use(ticketSeq + 0));
510 env.require(owners(alice, 250), tickets(alice, 250));
513 env(ticket::create(alice, 2),
514 ticket::use(ticketSeq + 1),
517 env.require(owners(alice, 249), tickets(alice, 249));
520 env(ticket::create(alice, 2), ticket::use(ticketSeq + 2));
523 env.require(owners(alice, 250), tickets(alice, 250));
529 env.require(owners(alice, 250), tickets(alice, 250));
534 Account alice{
"alice"};
536 env.fund(XRP(100000), alice);
540 env(ticket::create(alice, 2));
543 env.require(owners(alice, 2), tickets(alice, 2));
547 env(ticket::create(alice, 250),
548 ticket::use(ticketSeq_AB + 0),
551 env.require(owners(alice, 1), tickets(alice, 1));
557 env.require(owners(alice, 1), tickets(alice, 1));
560 env(ticket::create(alice, 250), ticket::use(ticketSeq_AB + 1));
563 env.require(owners(alice, 250), tickets(alice, 250));
570 testcase(
"Create Ticket Insufficient Reserve");
572 using namespace test::jtx;
574 Account alice{
"alice"};
577 env.fund(env.current()->fees().accountReserve(1) - drops(1), alice);
582 env.require(owners(alice, 0), tickets(alice, 0));
588 env.current()->fees().accountReserve(1) - env.balance(alice)));
591 env(ticket::create(alice, 1));
594 env.require(owners(alice, 1), tickets(alice, 1));
601 env.current()->fees().accountReserve(250) - drops(1) -
602 env.balance(alice)));
609 env.require(owners(alice, 1), tickets(alice, 1));
616 env.current()->fees().accountReserve(250) - env.balance(alice)));
620 env(ticket::create(alice, 249));
623 env.require(owners(alice, 250), tickets(alice, 250));
624 BEAST_EXPECT(ticketSeq + 249 == env.seq(alice));
632 using namespace test::jtx;
634 Account alice{
"alice"};
636 env.fund(XRP(10000), alice);
641 env(ticket::create(alice, 2));
644 env.require(owners(alice, 2), tickets(alice, 2));
645 BEAST_EXPECT(ticketSeq_AB + 2 == env.seq(alice));
649 env(ticket::create(alice, 1), ticket::use(ticketSeq_AB + 0));
652 env.require(owners(alice, 2), tickets(alice, 2));
653 BEAST_EXPECT(ticketSeq_C + 1 == env.seq(alice));
657 env(ticket::create(alice, 2), ticket::use(ticketSeq_AB + 1));
660 env.require(owners(alice, 3), tickets(alice, 3));
661 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
664 env(
noop(alice), ticket::use(ticketSeq_DE + 0));
667 env.require(owners(alice, 2), tickets(alice, 2));
668 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
670 env(pay(alice, env.master, XRP(20)), ticket::use(ticketSeq_DE + 1));
673 env.require(owners(alice, 1), tickets(alice, 1));
674 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
676 env(trust(alice, env.master[
"USD"](20)), ticket::use(ticketSeq_C));
679 env.require(owners(alice, 1), tickets(alice, 0));
680 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
692 env(ticket::create(alice, 1));
695 env.require(owners(alice, 1), tickets(alice, 0));
696 BEAST_EXPECT(ticketSeq_F + 1 == env.seq(alice));
701 env(ticket::create(alice, 1));
706 ticket::use(ticketSeq_G),
707 json(R
"({"AccountTxnID": "0"})"),
710 env.require(owners(alice, 2), tickets(alice, 1));
726 testcase(
"Transaction Database With Tickets");
728 using namespace test::jtx;
730 Account alice{
"alice"};
732 env.fund(XRP(10000), alice);
736 auto getTxID = [&env,
this]() ->
uint256 {
738 if (!BEAST_EXPECTS(tx,
"Transaction not found"))
739 Throw<std::invalid_argument>(
"Invalid transaction ID");
741 return tx->getTransactionID();
755 env(ticket::create(alice, ticketCount));
756 uint256 const txHash_1{getTxID()};
759 ticketSeq += ticketCount;
760 env(
noop(alice), ticket::use(--ticketSeq));
761 uint256 const txHash_2{getTxID()};
763 env(pay(alice, env.master, XRP(200)), ticket::use(--ticketSeq));
764 uint256 const txHash_3{getTxID()};
766 env(deposit::auth(alice, env.master), ticket::use(--ticketSeq));
767 uint256 const txHash_4{getTxID()};
773 env(pay(alice, env.master, XRP(300)), ticket::use(--ticketSeq));
774 uint256 const txHash_5{getTxID()};
776 env(pay(alice, env.master, XRP(400)), ticket::use(--ticketSeq));
777 uint256 const txHash_6{getTxID()};
779 env(deposit::unauth(alice, env.master), ticket::use(--ticketSeq));
780 uint256 const txHash_7{getTxID()};
782 env(
noop(alice), ticket::use(--ticketSeq));
783 uint256 const txHash_8{getTxID()};
793 auto checkTxFromDB = [&env,
this](
810 BEAST_EXPECT(tx->getLedger() == ledgerSeq);
812 BEAST_EXPECT((*sttx)[sfSequence] == txSeq);
814 BEAST_EXPECT((*sttx)[sfTicketSequence] == *ticketSeq);
815 BEAST_EXPECT((*sttx)[sfTransactionType] == txType);
819 fail(
"Expected transaction was not found");
824 checkTxFromDB(txHash_1, 4, 4, {}, ttTICKET_CREATE);
825 checkTxFromDB(txHash_2, 4, 0, 13, ttACCOUNT_SET);
826 checkTxFromDB(txHash_3, 4, 0, 12, ttPAYMENT);
827 checkTxFromDB(txHash_4, 4, 0, 11, ttDEPOSIT_PREAUTH);
829 checkTxFromDB(txHash_5, 5, 0, 10, ttPAYMENT);
830 checkTxFromDB(txHash_6, 5, 0, 9, ttPAYMENT);
831 checkTxFromDB(txHash_7, 5, 0, 8, ttDEPOSIT_PREAUTH);
832 checkTxFromDB(txHash_8, 5, 0, 7, ttACCOUNT_SET);
842 testcase(
"Sign with TicketSequence");
844 using namespace test::jtx;
846 Account alice{
"alice"};
848 env.fund(XRP(10000), alice);
853 env(ticket::create(alice, 2));
856 env.require(owners(alice, 2), tickets(alice, 2));
857 BEAST_EXPECT(ticketSeq + 2 == env.seq(alice));
866 tx[jss::tx_json] =
noop(alice);
867 tx[jss::tx_json][sfTicketSequence.jsonName] = ticketSeq;
871 BEAST_EXPECT(!tx[jss::tx_json].isMember(sfSequence.jsonName));
878 if (BEAST_EXPECT(jr[jss::result][jss::tx_json].isMember(
879 sfSequence.jsonName)))
882 jr[jss::result][jss::tx_json][sfSequence.jsonName] == 0);
887 env.require(owners(alice, 2), tickets(alice, 2));
890 env.rpc(
"submit", jr[jss::result][jss::tx_blob].asString());
892 env.require(owners(alice, 1), tickets(alice, 1));
901 tx[jss::tx_json] =
noop(alice);
902 tx[jss::tx_json][sfTicketSequence.jsonName] = ticketSeq + 1;
906 BEAST_EXPECT(!tx[jss::tx_json].isMember(sfSequence.jsonName));
913 if (BEAST_EXPECT(jr[jss::result][jss::tx_json].isMember(
914 sfSequence.jsonName)))
917 jr[jss::result][jss::tx_json][sfSequence.jsonName] == 0);
922 env.require(owners(alice, 0), tickets(alice, 0));
931 testcase(
"Fix both Seq and Ticket");
934 using namespace test::jtx;
936 Env env{*
this, testable_amendments() - featureTicketBatch};
937 Account alice{
"alice"};
939 env.fund(XRP(10000), alice);
946 env.require(owners(alice, 0), tickets(alice, 0));
947 BEAST_EXPECT(ticketSeq == env.seq(alice) + 1);
953 ticket::use(ticketSeq),
960 Env env{*
this, testable_amendments()};
961 Account alice{
"alice"};
963 env.fund(XRP(10000), alice);
968 env(ticket::create(alice, 1));
970 env.require(owners(alice, 1), tickets(alice, 1));
971 BEAST_EXPECT(ticketSeq + 1 == env.seq(alice));
976 ticket::use(ticketSeq),
983 env.require(owners(alice, 1), tickets(alice, 1));
984 BEAST_EXPECT(ticketSeq + 1 == env.seq(alice));