56 using namespace std::string_literals;
63 txType == jss::TicketCreate,
"Unexpected TransactionType: "s + txType))
68 if (!BEAST_EXPECTS(count >= 1,
"Unexpected ticket count: "s +
std::to_string(count)))
76 metadata.
isMember(sfTransactionResult.jsonName) &&
77 metadata[sfTransactionResult.jsonName].
asString() ==
"tesSUCCESS",
78 "Not metadata for successful TicketCreate."))
81 BEAST_EXPECT(metadata.
isMember(sfAffectedNodes.jsonName));
82 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].
isArray());
84 bool directoryChanged =
false;
88 for (
json::Value const& node : metadata[sfAffectedNodes.jsonName])
90 if (node.isMember(sfModifiedNode.jsonName))
92 json::Value const& modified = node[sfModifiedNode.jsonName];
94 if (entryType == jss::AccountRoot)
96 auto const& previousFields = modified[sfPreviousFields.jsonName];
97 auto const& finalFields = modified[sfFinalFields.jsonName];
100 std::uint32_t const prevSeq = previousFields[sfSequence.jsonName].asUInt();
102 acctRootFinalSeq = finalFields[sfSequence.jsonName].asUInt();
107 BEAST_EXPECT(acctRootFinalSeq == prevSeq + count);
112 BEAST_EXPECT(prevSeq == txSeq);
113 BEAST_EXPECT(acctRootFinalSeq == prevSeq + count + 1);
117 std::uint32_t const consumedTickets = {txSeq == 0u ? 1u : 0u};
126 bool const unreportedPrevTicketCount = {count == 1 && txSeq == 0};
129 if (unreportedPrevTicketCount)
133 BEAST_EXPECT(!previousFields.isMember(sfOwnerCount.jsonName));
139 previousFields[sfOwnerCount.jsonName].asUInt()};
142 finalFields[sfOwnerCount.jsonName].asUInt()};
144 BEAST_EXPECT(prevCount + count - consumedTickets == finalCount);
148 BEAST_EXPECT(finalFields.isMember(sfTicketCount.jsonName));
150 if (unreportedPrevTicketCount)
154 BEAST_EXPECT(!previousFields.isMember(sfTicketCount.jsonName));
161 previousFields.isMember(sfTicketCount.jsonName)
162 ? previousFields[sfTicketCount.jsonName].asUInt()
166 (startCount == 0u) ^ previousFields.isMember(sfTicketCount.jsonName));
169 startCount + count - consumedTickets ==
170 finalFields[sfTicketCount.jsonName]);
173 else if (entryType == jss::DirectoryNode)
175 directoryChanged =
true;
179 fail(
"Unexpected modified node: "s + entryType, __FILE__, __LINE__);
182 else if (node.isMember(sfCreatedNode.jsonName))
184 json::Value const& created = node[sfCreatedNode.jsonName];
186 if (entryType == jss::Ticket)
188 auto const& newFields = created[sfNewFields.jsonName];
190 BEAST_EXPECT(newFields[sfAccount.jsonName].asString() == account);
191 ticketSeqs.
push_back(newFields[sfTicketSequence.jsonName].asUInt());
193 else if (entryType == jss::DirectoryNode)
195 directoryChanged =
true;
199 fail(
"Unexpected created node: "s + entryType, __FILE__, __LINE__);
202 else if (node.isMember(sfDeletedNode.jsonName))
204 json::Value const& deleted = node[sfDeletedNode.jsonName];
207 if (entryType == jss::Ticket)
210 BEAST_EXPECT(txSeq == 0);
213 auto const& finalFields = deleted[sfFinalFields.jsonName];
214 BEAST_EXPECT(finalFields[sfAccount.jsonName].asString() == account);
218 finalFields[sfTicketSequence.jsonName].asUInt() ==
219 tx[sfTicketSequence.jsonName].
asUInt());
224 fail(
"Unexpected node type in TicketCreate metadata.", __FILE__, __LINE__);
227 BEAST_EXPECT(directoryChanged);
230 BEAST_EXPECT(ticketSeqs.
size() == count);
233 BEAST_EXPECT(*ticketSeqs.
rbegin() == acctRootFinalSeq - 1);
268 BEAST_EXPECT(tx[sfSequence.jsonName].
asUInt() == 0);
271 tx.
isMember(sfTicketSequence.jsonName),
272 "Not metadata for a ticket consuming transaction."))
279 metadata.
isMember(sfTransactionResult.jsonName),
280 "Metadata is missing TransactionResult."))
286 transactionResult ==
"tesSUCCESS" ||
287 transactionResult.
compare(0, 3,
"tec") == 0,
288 transactionResult +
" neither tesSUCCESS nor tec"))
292 BEAST_EXPECT(metadata.
isMember(sfAffectedNodes.jsonName));
293 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].
isArray());
295 bool acctRootFound{
false};
297 int ticketsRemoved{0};
298 for (
json::Value const& node : metadata[sfAffectedNodes.jsonName])
300 if (node.isMember(sfModifiedNode.jsonName))
302 json::Value const& modified{node[sfModifiedNode.jsonName]};
304 if (entryType ==
"AccountRoot" &&
305 modified[sfFinalFields.jsonName][sfAccount.jsonName].
asString() == account)
307 acctRootFound =
true;
309 auto const& previousFields = modified[sfPreviousFields.jsonName];
310 auto const& finalFields = modified[sfFinalFields.jsonName];
312 acctRootSeq = finalFields[sfSequence.jsonName].asUInt();
318 previousFields.isMember(sfTicketCount.jsonName),
319 "AccountRoot previous is missing TicketCount"))
323 previousFields[sfTicketCount.jsonName].asUInt();
325 BEAST_EXPECT(prevTicketCount > 0);
326 if (prevTicketCount == 1)
328 BEAST_EXPECT(!finalFields.isMember(sfTicketCount.jsonName));
333 finalFields.isMember(sfTicketCount.jsonName) &&
334 finalFields[sfTicketCount.jsonName].asUInt() == prevTicketCount - 1);
338 else if (node.isMember(sfDeletedNode.jsonName))
340 json::Value const& deleted{node[sfDeletedNode.jsonName]};
343 if (entryType == jss::Ticket)
347 deleted[sfFinalFields.jsonName][sfAccount.jsonName].
asString() == account);
351 deleted[sfFinalFields.jsonName][sfTicketSequence.jsonName].
asUInt() ==
358 BEAST_EXPECT(acctRootFound);
359 BEAST_EXPECT(ticketsRemoved == 1);
360 BEAST_EXPECT(ticketSeq < acctRootSeq);
366 testcase(
"Create Tickets that fail Preflight");
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(ticketSeqA));
402 env.require(Owners(master, 1), tickets(master, 1));
404 env(ticket::create(master, 250), ticket::Use(ticketSeqB));
407 env.require(Owners(master, 250), tickets(master, 250));
413 testcase(
"Create Tickets that fail Preclaim");
422 env(ticket::create(alice, 1), Json(jss::Sequence, 1), Ter(
terNO_ACCOUNT));
430 env.fund(XRP(100000), alice);
433 env(ticket::create(alice, 250));
436 env.require(Owners(alice, 250), tickets(alice, 250));
440 env(ticket::create(alice, 1), ticket::Use(ticketSeq + 0));
443 env.require(Owners(alice, 250), tickets(alice, 250));
446 env(ticket::create(alice, 2), ticket::Use(ticketSeq + 1), Ter(
tecDIR_FULL));
448 env.require(Owners(alice, 249), tickets(alice, 249));
451 env(ticket::create(alice, 2), ticket::Use(ticketSeq + 2));
454 env.require(Owners(alice, 250), tickets(alice, 250));
460 env.require(Owners(alice, 250), tickets(alice, 250));
467 env.fund(XRP(100000), alice);
471 env(ticket::create(alice, 2));
474 env.require(Owners(alice, 2), tickets(alice, 2));
478 env(ticket::create(alice, 250), ticket::Use(ticketSeqAb + 0), Ter(
tecDIR_FULL));
480 env.require(Owners(alice, 1), tickets(alice, 1));
486 env.require(Owners(alice, 1), tickets(alice, 1));
489 env(ticket::create(alice, 250), ticket::Use(ticketSeqAb + 1));
492 env.require(Owners(alice, 250), tickets(alice, 250));
499 testcase(
"Create Ticket Insufficient Reserve");
506 env.fund(env.current()->fees().accountReserve(1) - drops(1), alice);
511 env.require(Owners(alice, 0), tickets(alice, 0));
514 env(pay(env.master, alice, env.current()->fees().accountReserve(1) - env.balance(alice)));
517 env(ticket::create(alice, 1));
520 env.require(Owners(alice, 1), tickets(alice, 1));
527 env.current()->fees().accountReserve(250) - drops(1) - env.balance(alice)));
534 env.require(Owners(alice, 1), tickets(alice, 1));
538 env(pay(env.master, alice, env.current()->fees().accountReserve(250) - env.balance(alice)));
542 env(ticket::create(alice, 249));
545 env.require(Owners(alice, 250), tickets(alice, 250));
546 BEAST_EXPECT(ticketSeq + 249 == env.seq(alice));
558 env.fund(XRP(10000), alice);
563 env(ticket::create(alice, 2));
566 env.require(Owners(alice, 2), tickets(alice, 2));
567 BEAST_EXPECT(ticketSeqAb + 2 == env.seq(alice));
571 env(ticket::create(alice, 1), ticket::Use(ticketSeqAb + 0));
574 env.require(Owners(alice, 2), tickets(alice, 2));
575 BEAST_EXPECT(ticketSeqC + 1 == env.seq(alice));
579 env(ticket::create(alice, 2), ticket::Use(ticketSeqAb + 1));
582 env.require(Owners(alice, 3), tickets(alice, 3));
583 BEAST_EXPECT(ticketSeqDe + 2 == env.seq(alice));
586 env(noop(alice), ticket::Use(ticketSeqDe + 0));
589 env.require(Owners(alice, 2), tickets(alice, 2));
590 BEAST_EXPECT(ticketSeqDe + 2 == env.seq(alice));
592 env(pay(alice, env.master, XRP(20)), ticket::Use(ticketSeqDe + 1));
595 env.require(Owners(alice, 1), tickets(alice, 1));
596 BEAST_EXPECT(ticketSeqDe + 2 == env.seq(alice));
598 env(trust(alice, env.master[
"USD"](20)), ticket::Use(ticketSeqC));
601 env.require(Owners(alice, 1), tickets(alice, 0));
602 BEAST_EXPECT(ticketSeqDe + 2 == env.seq(alice));
605 env(noop(alice), ticket::Use(ticketSeqC), Ter(
tefNO_TICKET));
610 env(noop(alice), ticket::Use(ticketSeqF), Ter(
terPRE_TICKET));
614 env(ticket::create(alice, 1));
617 env.require(Owners(alice, 1), tickets(alice, 0));
618 BEAST_EXPECT(ticketSeqF + 1 == env.seq(alice));
623 env(ticket::create(alice, 1));
628 ticket::Use(ticketSeqG),
629 Json(R
"({"AccountTxnID": "0"})"),
632 env.require(Owners(alice, 2), tickets(alice, 1));
648 testcase(
"Transaction Database With Tickets");
654 env.fund(XRP(10000), alice);
658 auto getTxID = [&env,
this]() ->
uint256 {
660 if (!BEAST_EXPECTS(tx,
"Transaction not found"))
663 return tx->getTransactionID();
677 env(ticket::create(alice, kTicketCount));
678 uint256 const txHash1{getTxID()};
681 ticketSeq += kTicketCount;
682 env(noop(alice), ticket::Use(--ticketSeq));
683 uint256 const txHash2{getTxID()};
685 env(pay(alice, env.master, XRP(200)), ticket::Use(--ticketSeq));
686 uint256 const txHash3{getTxID()};
688 env(deposit::auth(alice, env.master), ticket::Use(--ticketSeq));
689 uint256 const txHash4{getTxID()};
695 env(pay(alice, env.master, XRP(300)), ticket::Use(--ticketSeq));
696 uint256 const txHash5{getTxID()};
698 env(pay(alice, env.master, XRP(400)), ticket::Use(--ticketSeq));
699 uint256 const txHash6{getTxID()};
701 env(deposit::unauth(alice, env.master), ticket::Use(--ticketSeq));
702 uint256 const txHash7{getTxID()};
704 env(noop(alice), ticket::Use(--ticketSeq));
705 uint256 const txHash8{getTxID()};
715 auto checkTxFromDB = [&env,
this](
731 BEAST_EXPECT(tx->getLedger() == ledgerSeq);
733 BEAST_EXPECT((*sttx)[sfSequence] == txSeq);
735 BEAST_EXPECT((*sttx)[sfTicketSequence] == *ticketSeq);
736 BEAST_EXPECT((*sttx)[sfTransactionType] == txType);
740 fail(
"Expected transaction was not found");
745 checkTxFromDB(txHash1, 4, 4, {}, ttTICKET_CREATE);
746 checkTxFromDB(txHash2, 4, 0, 13, ttACCOUNT_SET);
747 checkTxFromDB(txHash3, 4, 0, 12, ttPAYMENT);
748 checkTxFromDB(txHash4, 4, 0, 11, ttDEPOSIT_PREAUTH);
750 checkTxFromDB(txHash5, 5, 0, 10, ttPAYMENT);
751 checkTxFromDB(txHash6, 5, 0, 9, ttPAYMENT);
752 checkTxFromDB(txHash7, 5, 0, 8, ttDEPOSIT_PREAUTH);
753 checkTxFromDB(txHash8, 5, 0, 7, ttACCOUNT_SET);
763 testcase(
"Sign with TicketSequence");
769 env.fund(XRP(10000), alice);
774 env(ticket::create(alice, 2));
777 env.require(Owners(alice, 2), tickets(alice, 2));
778 BEAST_EXPECT(ticketSeq + 2 == env.seq(alice));
787 tx[jss::tx_json] = noop(alice);
788 tx[jss::tx_json][sfTicketSequence.jsonName] = ticketSeq;
792 BEAST_EXPECT(!tx[jss::tx_json].isMember(sfSequence.jsonName));
799 if (BEAST_EXPECT(jr[jss::result][jss::tx_json].isMember(sfSequence.jsonName)))
801 BEAST_EXPECT(jr[jss::result][jss::tx_json][sfSequence.jsonName] == 0);
806 env.require(Owners(alice, 2), tickets(alice, 2));
809 env.rpc(
"submit", jr[jss::result][jss::tx_blob].asString());
811 env.require(Owners(alice, 1), tickets(alice, 1));
820 tx[jss::tx_json] = noop(alice);
821 tx[jss::tx_json][sfTicketSequence.jsonName] = ticketSeq + 1;
825 BEAST_EXPECT(!tx[jss::tx_json].isMember(sfSequence.jsonName));
832 if (BEAST_EXPECT(jr[jss::result][jss::tx_json].isMember(sfSequence.jsonName)))
834 BEAST_EXPECT(jr[jss::result][jss::tx_json][sfSequence.jsonName] == 0);
839 env.require(Owners(alice, 0), tickets(alice, 0));
850 testcase(
"Fix both Seq and Ticket");
852 Env env{*
this, testableAmendments()};
855 env.fund(XRP(10000), alice);
860 env(ticket::create(alice, 1));
862 env.require(Owners(alice, 1), tickets(alice, 1));
863 BEAST_EXPECT(ticketSeq + 1 == env.seq(alice));
867 env(noop(alice), ticket::Use(ticketSeq), Seq(env.seq(alice)), Ter(
temSEQ_AND_TICKET));
872 env.require(Owners(alice, 1), tickets(alice, 1));
873 BEAST_EXPECT(ticketSeq + 1 == env.seq(alice));