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);
366 testcase(
"Create Tickets that fail Preflight");
368 using namespace test::jtx;
371 Account
const master{env.master};
379 env(ticket::create(master, 1), fee(XRP(10)));
382 env.require(owners(master, 1), tickets(master, 1));
384 env(ticket::create(master, 1), fee(XRP(-1)), ter(
temBAD_FEE));
391 env.require(owners(master, 2), tickets(master, 2));
395 env.require(owners(master, 2), tickets(master, 2));
399 env(
noop(master), ticket::use(ticketSeq_A));
402 env.require(owners(master, 1), tickets(master, 1));
404 env(ticket::create(master, 250), ticket::use(ticketSeq_B));
407 env.require(owners(master, 250), tickets(master, 250));
413 testcase(
"Create Tickets that fail Preclaim");
415 using namespace test::jtx;
419 Account alice{
"alice"};
422 env(ticket::create(alice, 1),
423 json(jss::Sequence, 1),
430 Account alice{
"alice"};
432 env.fund(XRP(100000), alice);
435 env(ticket::create(alice, 250));
438 env.require(owners(alice, 250), tickets(alice, 250));
442 env(ticket::create(alice, 1), ticket::use(ticketSeq + 0));
445 env.require(owners(alice, 250), tickets(alice, 250));
448 env(ticket::create(alice, 2),
449 ticket::use(ticketSeq + 1),
452 env.require(owners(alice, 249), tickets(alice, 249));
455 env(ticket::create(alice, 2), ticket::use(ticketSeq + 2));
458 env.require(owners(alice, 250), tickets(alice, 250));
464 env.require(owners(alice, 250), tickets(alice, 250));
469 Account alice{
"alice"};
471 env.fund(XRP(100000), alice);
475 env(ticket::create(alice, 2));
478 env.require(owners(alice, 2), tickets(alice, 2));
482 env(ticket::create(alice, 250),
483 ticket::use(ticketSeq_AB + 0),
486 env.require(owners(alice, 1), tickets(alice, 1));
492 env.require(owners(alice, 1), tickets(alice, 1));
495 env(ticket::create(alice, 250), ticket::use(ticketSeq_AB + 1));
498 env.require(owners(alice, 250), tickets(alice, 250));
505 testcase(
"Create Ticket Insufficient Reserve");
507 using namespace test::jtx;
509 Account alice{
"alice"};
512 env.fund(env.current()->fees().accountReserve(1) - drops(1), alice);
517 env.require(owners(alice, 0), tickets(alice, 0));
523 env.current()->fees().accountReserve(1) - env.balance(alice)));
526 env(ticket::create(alice, 1));
529 env.require(owners(alice, 1), tickets(alice, 1));
536 env.current()->fees().accountReserve(250) - drops(1) -
537 env.balance(alice)));
544 env.require(owners(alice, 1), tickets(alice, 1));
551 env.current()->fees().accountReserve(250) - env.balance(alice)));
555 env(ticket::create(alice, 249));
558 env.require(owners(alice, 250), tickets(alice, 250));
559 BEAST_EXPECT(ticketSeq + 249 == env.seq(alice));
567 using namespace test::jtx;
569 Account alice{
"alice"};
571 env.fund(XRP(10000), alice);
576 env(ticket::create(alice, 2));
579 env.require(owners(alice, 2), tickets(alice, 2));
580 BEAST_EXPECT(ticketSeq_AB + 2 == env.seq(alice));
584 env(ticket::create(alice, 1), ticket::use(ticketSeq_AB + 0));
587 env.require(owners(alice, 2), tickets(alice, 2));
588 BEAST_EXPECT(ticketSeq_C + 1 == env.seq(alice));
592 env(ticket::create(alice, 2), ticket::use(ticketSeq_AB + 1));
595 env.require(owners(alice, 3), tickets(alice, 3));
596 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
599 env(
noop(alice), ticket::use(ticketSeq_DE + 0));
602 env.require(owners(alice, 2), tickets(alice, 2));
603 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
605 env(pay(alice, env.master, XRP(20)), ticket::use(ticketSeq_DE + 1));
608 env.require(owners(alice, 1), tickets(alice, 1));
609 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
611 env(trust(alice, env.master[
"USD"](20)), ticket::use(ticketSeq_C));
614 env.require(owners(alice, 1), tickets(alice, 0));
615 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
627 env(ticket::create(alice, 1));
630 env.require(owners(alice, 1), tickets(alice, 0));
631 BEAST_EXPECT(ticketSeq_F + 1 == env.seq(alice));
636 env(ticket::create(alice, 1));
641 ticket::use(ticketSeq_G),
642 json(R
"({"AccountTxnID": "0"})"),
645 env.require(owners(alice, 2), tickets(alice, 1));
661 testcase(
"Transaction Database With Tickets");
663 using namespace test::jtx;
665 Account alice{
"alice"};
667 env.fund(XRP(10000), alice);
671 auto getTxID = [&env,
this]() ->
uint256 {
673 if (!BEAST_EXPECTS(tx,
"Transaction not found"))
674 Throw<std::invalid_argument>(
"Invalid transaction ID");
676 return tx->getTransactionID();
690 env(ticket::create(alice, ticketCount));
691 uint256 const txHash_1{getTxID()};
694 ticketSeq += ticketCount;
695 env(
noop(alice), ticket::use(--ticketSeq));
696 uint256 const txHash_2{getTxID()};
698 env(pay(alice, env.master, XRP(200)), ticket::use(--ticketSeq));
699 uint256 const txHash_3{getTxID()};
701 env(deposit::auth(alice, env.master), ticket::use(--ticketSeq));
702 uint256 const txHash_4{getTxID()};
708 env(pay(alice, env.master, XRP(300)), ticket::use(--ticketSeq));
709 uint256 const txHash_5{getTxID()};
711 env(pay(alice, env.master, XRP(400)), ticket::use(--ticketSeq));
712 uint256 const txHash_6{getTxID()};
714 env(deposit::unauth(alice, env.master), ticket::use(--ticketSeq));
715 uint256 const txHash_7{getTxID()};
717 env(
noop(alice), ticket::use(--ticketSeq));
718 uint256 const txHash_8{getTxID()};
728 auto checkTxFromDB = [&env,
this](
745 BEAST_EXPECT(tx->getLedger() == ledgerSeq);
747 BEAST_EXPECT((*sttx)[sfSequence] == txSeq);
749 BEAST_EXPECT((*sttx)[sfTicketSequence] == *ticketSeq);
750 BEAST_EXPECT((*sttx)[sfTransactionType] == txType);
754 fail(
"Expected transaction was not found");
759 checkTxFromDB(txHash_1, 4, 4, {}, ttTICKET_CREATE);
760 checkTxFromDB(txHash_2, 4, 0, 13, ttACCOUNT_SET);
761 checkTxFromDB(txHash_3, 4, 0, 12, ttPAYMENT);
762 checkTxFromDB(txHash_4, 4, 0, 11, ttDEPOSIT_PREAUTH);
764 checkTxFromDB(txHash_5, 5, 0, 10, ttPAYMENT);
765 checkTxFromDB(txHash_6, 5, 0, 9, ttPAYMENT);
766 checkTxFromDB(txHash_7, 5, 0, 8, ttDEPOSIT_PREAUTH);
767 checkTxFromDB(txHash_8, 5, 0, 7, ttACCOUNT_SET);
777 testcase(
"Sign with TicketSequence");
779 using namespace test::jtx;
781 Account alice{
"alice"};
783 env.fund(XRP(10000), alice);
788 env(ticket::create(alice, 2));
791 env.require(owners(alice, 2), tickets(alice, 2));
792 BEAST_EXPECT(ticketSeq + 2 == env.seq(alice));
801 tx[jss::tx_json] =
noop(alice);
802 tx[jss::tx_json][sfTicketSequence.jsonName] = ticketSeq;
806 BEAST_EXPECT(!tx[jss::tx_json].isMember(sfSequence.jsonName));
813 if (BEAST_EXPECT(jr[jss::result][jss::tx_json].isMember(
814 sfSequence.jsonName)))
817 jr[jss::result][jss::tx_json][sfSequence.jsonName] == 0);
822 env.require(owners(alice, 2), tickets(alice, 2));
825 env.rpc(
"submit", jr[jss::result][jss::tx_blob].asString());
827 env.require(owners(alice, 1), tickets(alice, 1));
836 tx[jss::tx_json] =
noop(alice);
837 tx[jss::tx_json][sfTicketSequence.jsonName] = ticketSeq + 1;
841 BEAST_EXPECT(!tx[jss::tx_json].isMember(sfSequence.jsonName));
848 if (BEAST_EXPECT(jr[jss::result][jss::tx_json].isMember(
849 sfSequence.jsonName)))
852 jr[jss::result][jss::tx_json][sfSequence.jsonName] == 0);
857 env.require(owners(alice, 0), tickets(alice, 0));
864 using namespace test::jtx;
868 testcase(
"Fix both Seq and Ticket");
870 Env env{*
this, testable_amendments()};
871 Account alice{
"alice"};
873 env.fund(XRP(10000), alice);
878 env(ticket::create(alice, 1));
880 env.require(owners(alice, 1), tickets(alice, 1));
881 BEAST_EXPECT(ticketSeq + 1 == env.seq(alice));
886 ticket::use(ticketSeq),
893 env.require(owners(alice, 1), tickets(alice, 1));
894 BEAST_EXPECT(ticketSeq + 1 == env.seq(alice));