58 using namespace std::string_literals;
65 txType == jss::TicketCreate,
"Unexpected TransactionType: "s + txType))
70 if (!BEAST_EXPECTS(count >= 1,
"Unexpected ticket count: "s +
std::to_string(count)))
78 metadata.
isMember(sfTransactionResult.jsonName) &&
79 metadata[sfTransactionResult.jsonName].
asString() ==
"tesSUCCESS",
80 "Not metadata for successful TicketCreate."))
83 BEAST_EXPECT(metadata.
isMember(sfAffectedNodes.jsonName));
84 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].
isArray());
86 bool directoryChanged =
false;
90 for (
json::Value const& node : metadata[sfAffectedNodes.jsonName])
92 if (node.isMember(sfModifiedNode.jsonName))
94 json::Value const& modified = node[sfModifiedNode.jsonName];
96 if (entryType == jss::AccountRoot)
98 auto const& previousFields = modified[sfPreviousFields.jsonName];
99 auto const& finalFields = modified[sfFinalFields.jsonName];
102 std::uint32_t const prevSeq = previousFields[sfSequence.jsonName].asUInt();
104 acctRootFinalSeq = finalFields[sfSequence.jsonName].asUInt();
109 BEAST_EXPECT(acctRootFinalSeq == prevSeq + count);
114 BEAST_EXPECT(prevSeq == txSeq);
115 BEAST_EXPECT(acctRootFinalSeq == prevSeq + count + 1);
119 std::uint32_t const consumedTickets = {txSeq == 0u ? 1u : 0u};
128 bool const unreportedPrevTicketCount = {count == 1 && txSeq == 0};
131 if (unreportedPrevTicketCount)
135 BEAST_EXPECT(!previousFields.isMember(sfOwnerCount.jsonName));
141 previousFields[sfOwnerCount.jsonName].asUInt()};
144 finalFields[sfOwnerCount.jsonName].asUInt()};
146 BEAST_EXPECT(prevCount + count - consumedTickets == finalCount);
150 BEAST_EXPECT(finalFields.isMember(sfTicketCount.jsonName));
152 if (unreportedPrevTicketCount)
156 BEAST_EXPECT(!previousFields.isMember(sfTicketCount.jsonName));
163 previousFields.isMember(sfTicketCount.jsonName)
164 ? previousFields[sfTicketCount.jsonName].asUInt()
168 (startCount == 0u) ^ previousFields.isMember(sfTicketCount.jsonName));
171 startCount + count - consumedTickets ==
172 finalFields[sfTicketCount.jsonName]);
175 else if (entryType == jss::DirectoryNode)
177 directoryChanged =
true;
181 fail(
"Unexpected modified node: "s + entryType, __FILE__, __LINE__);
184 else if (node.isMember(sfCreatedNode.jsonName))
186 json::Value const& created = node[sfCreatedNode.jsonName];
188 if (entryType == jss::Ticket)
190 auto const& newFields = created[sfNewFields.jsonName];
192 BEAST_EXPECT(newFields[sfAccount.jsonName].asString() == account);
193 ticketSeqs.
push_back(newFields[sfTicketSequence.jsonName].asUInt());
195 else if (entryType == jss::DirectoryNode)
197 directoryChanged =
true;
201 fail(
"Unexpected created node: "s + entryType, __FILE__, __LINE__);
204 else if (node.isMember(sfDeletedNode.jsonName))
206 json::Value const& deleted = node[sfDeletedNode.jsonName];
209 if (entryType == jss::Ticket)
212 BEAST_EXPECT(txSeq == 0);
215 auto const& finalFields = deleted[sfFinalFields.jsonName];
216 BEAST_EXPECT(finalFields[sfAccount.jsonName].asString() == account);
220 finalFields[sfTicketSequence.jsonName].asUInt() ==
221 tx[sfTicketSequence.jsonName].
asUInt());
226 fail(
"Unexpected node type in TicketCreate metadata.", __FILE__, __LINE__);
229 BEAST_EXPECT(directoryChanged);
232 BEAST_EXPECT(ticketSeqs.
size() == count);
235 BEAST_EXPECT(*ticketSeqs.
rbegin() == acctRootFinalSeq - 1);
272 BEAST_EXPECT(tx[sfSequence.jsonName].
asUInt() == 0);
275 tx.
isMember(sfTicketSequence.jsonName),
276 "Not metadata for a ticket consuming transaction."))
283 metadata.
isMember(sfTransactionResult.jsonName),
284 "Metadata is missing TransactionResult."))
290 transactionResult ==
"tesSUCCESS" ||
291 transactionResult.
compare(0, 3,
"tec") == 0,
292 transactionResult +
" neither tesSUCCESS nor tec"))
296 BEAST_EXPECT(metadata.
isMember(sfAffectedNodes.jsonName));
297 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].
isArray());
299 bool acctRootFound{
false};
301 int ticketsRemoved{0};
302 for (
json::Value const& node : metadata[sfAffectedNodes.jsonName])
304 if (node.isMember(sfModifiedNode.jsonName))
306 json::Value const& modified{node[sfModifiedNode.jsonName]};
308 if (entryType ==
"AccountRoot" &&
309 modified[sfFinalFields.jsonName][sfAccount.jsonName].
asString() == account)
311 acctRootFound =
true;
313 auto const& previousFields = modified[sfPreviousFields.jsonName];
314 auto const& finalFields = modified[sfFinalFields.jsonName];
316 acctRootSeq = finalFields[sfSequence.jsonName].asUInt();
322 previousFields.isMember(sfTicketCount.jsonName),
323 "AccountRoot previous is missing TicketCount"))
327 previousFields[sfTicketCount.jsonName].asUInt();
329 BEAST_EXPECT(prevTicketCount > 0);
330 if (prevTicketCount == 1)
332 BEAST_EXPECT(!finalFields.isMember(sfTicketCount.jsonName));
337 finalFields.isMember(sfTicketCount.jsonName) &&
338 finalFields[sfTicketCount.jsonName].asUInt() == prevTicketCount - 1);
342 else if (node.isMember(sfDeletedNode.jsonName))
344 json::Value const& deleted{node[sfDeletedNode.jsonName]};
347 if (entryType == jss::Ticket)
351 deleted[sfFinalFields.jsonName][sfAccount.jsonName].
asString() == account);
355 deleted[sfFinalFields.jsonName][sfTicketSequence.jsonName].
asUInt() ==
362 BEAST_EXPECT(acctRootFound);
363 BEAST_EXPECT(ticketsRemoved == 1);
364 BEAST_EXPECT(ticketSeq < acctRootSeq);
370 testcase(
"Create Tickets that fail Preflight");
375 Account const master{env.master};
383 env(ticket::create(master, 1), Fee(XRP(10)));
386 env.require(Owners(master, 1), tickets(master, 1));
388 env(ticket::create(master, 1), Fee(XRP(-1)), Ter(
temBAD_FEE));
395 env.require(Owners(master, 2), tickets(master, 2));
399 env.require(Owners(master, 2), tickets(master, 2));
403 env(noop(master), ticket::Use(ticketSeqA));
406 env.require(Owners(master, 1), tickets(master, 1));
408 env(ticket::create(master, 250), ticket::Use(ticketSeqB));
411 env.require(Owners(master, 250), tickets(master, 250));
417 testcase(
"Create Tickets that fail Preclaim");
426 env(ticket::create(alice, 1), Json(jss::Sequence, 1), Ter(
terNO_ACCOUNT));
434 env.fund(XRP(100000), alice);
437 env(ticket::create(alice, 250));
440 env.require(Owners(alice, 250), tickets(alice, 250));
444 env(ticket::create(alice, 1), ticket::Use(ticketSeq + 0));
447 env.require(Owners(alice, 250), tickets(alice, 250));
450 env(ticket::create(alice, 2), ticket::Use(ticketSeq + 1), Ter(
tecDIR_FULL));
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));
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), ticket::Use(ticketSeqAb + 0), Ter(
tecDIR_FULL));
484 env.require(Owners(alice, 1), tickets(alice, 1));
490 env.require(Owners(alice, 1), tickets(alice, 1));
493 env(ticket::create(alice, 250), ticket::Use(ticketSeqAb + 1));
496 env.require(Owners(alice, 250), tickets(alice, 250));
503 testcase(
"Create Ticket Insufficient Reserve");
510 env.fund(env.current()->fees().accountReserve(1, 1) - drops(1), alice);
515 env.require(Owners(alice, 0), tickets(alice, 0));
519 env.master, alice, env.current()->fees().accountReserve(1, 1) - env.balance(alice)));
522 env(ticket::create(alice, 1));
525 env.require(Owners(alice, 1), tickets(alice, 1));
532 env.current()->fees().accountReserve(250, 1) - drops(1) - env.balance(alice)));
539 env.require(Owners(alice, 1), tickets(alice, 1));
544 env.master, alice, env.current()->fees().accountReserve(250, 1) - env.balance(alice)));
548 env(ticket::create(alice, 249));
551 env.require(Owners(alice, 250), tickets(alice, 250));
552 BEAST_EXPECT(ticketSeq + 249 == env.seq(alice));
564 env.fund(XRP(10000), alice);
569 env(ticket::create(alice, 2));
572 env.require(Owners(alice, 2), tickets(alice, 2));
573 BEAST_EXPECT(ticketSeqAb + 2 == env.seq(alice));
577 env(ticket::create(alice, 1), ticket::Use(ticketSeqAb + 0));
580 env.require(Owners(alice, 2), tickets(alice, 2));
581 BEAST_EXPECT(ticketSeqC + 1 == env.seq(alice));
585 env(ticket::create(alice, 2), ticket::Use(ticketSeqAb + 1));
588 env.require(Owners(alice, 3), tickets(alice, 3));
589 BEAST_EXPECT(ticketSeqDe + 2 == env.seq(alice));
592 env(noop(alice), ticket::Use(ticketSeqDe + 0));
595 env.require(Owners(alice, 2), tickets(alice, 2));
596 BEAST_EXPECT(ticketSeqDe + 2 == env.seq(alice));
598 env(pay(alice, env.master, XRP(20)), ticket::Use(ticketSeqDe + 1));
601 env.require(Owners(alice, 1), tickets(alice, 1));
602 BEAST_EXPECT(ticketSeqDe + 2 == env.seq(alice));
604 env(trust(alice, env.master[
"USD"](20)), ticket::Use(ticketSeqC));
607 env.require(Owners(alice, 1), tickets(alice, 0));
608 BEAST_EXPECT(ticketSeqDe + 2 == env.seq(alice));
611 env(noop(alice), ticket::Use(ticketSeqC), Ter(
tefNO_TICKET));
616 env(noop(alice), ticket::Use(ticketSeqF), Ter(
terPRE_TICKET));
620 env(ticket::create(alice, 1));
623 env.require(Owners(alice, 1), tickets(alice, 0));
624 BEAST_EXPECT(ticketSeqF + 1 == env.seq(alice));
629 env(ticket::create(alice, 1));
634 ticket::Use(ticketSeqG),
635 Json(R
"({"AccountTxnID": "0"})"),
638 env.require(Owners(alice, 2), tickets(alice, 1));
654 testcase(
"Transaction Database With Tickets");
660 env.fund(XRP(10000), alice);
664 auto getTxID = [&env,
this]() ->
uint256 {
666 if (!BEAST_EXPECTS(tx,
"Transaction not found"))
669 return tx->getTransactionID();
683 env(ticket::create(alice, kTicketCount));
684 uint256 const txHash1{getTxID()};
687 ticketSeq += kTicketCount;
688 env(noop(alice), ticket::Use(--ticketSeq));
689 uint256 const txHash2{getTxID()};
691 env(pay(alice, env.master, XRP(200)), ticket::Use(--ticketSeq));
692 uint256 const txHash3{getTxID()};
694 env(deposit::auth(alice, env.master), ticket::Use(--ticketSeq));
695 uint256 const txHash4{getTxID()};
701 env(pay(alice, env.master, XRP(300)), ticket::Use(--ticketSeq));
702 uint256 const txHash5{getTxID()};
704 env(pay(alice, env.master, XRP(400)), ticket::Use(--ticketSeq));
705 uint256 const txHash6{getTxID()};
707 env(deposit::unauth(alice, env.master), ticket::Use(--ticketSeq));
708 uint256 const txHash7{getTxID()};
710 env(noop(alice), ticket::Use(--ticketSeq));
711 uint256 const txHash8{getTxID()};
721 auto checkTxFromDB = [&env,
this](
737 BEAST_EXPECT(tx->getLedger() == ledgerSeq);
739 BEAST_EXPECT((*sttx)[sfSequence] == txSeq);
741 BEAST_EXPECT((*sttx)[sfTicketSequence] == *ticketSeq);
742 BEAST_EXPECT((*sttx)[sfTransactionType] == txType);
746 fail(
"Expected transaction was not found");
751 checkTxFromDB(txHash1, 4, 4, {}, ttTICKET_CREATE);
752 checkTxFromDB(txHash2, 4, 0, 13, ttACCOUNT_SET);
753 checkTxFromDB(txHash3, 4, 0, 12, ttPAYMENT);
754 checkTxFromDB(txHash4, 4, 0, 11, ttDEPOSIT_PREAUTH);
756 checkTxFromDB(txHash5, 5, 0, 10, ttPAYMENT);
757 checkTxFromDB(txHash6, 5, 0, 9, ttPAYMENT);
758 checkTxFromDB(txHash7, 5, 0, 8, ttDEPOSIT_PREAUTH);
759 checkTxFromDB(txHash8, 5, 0, 7, ttACCOUNT_SET);
769 testcase(
"Sign with TicketSequence");
775 env.fund(XRP(10000), alice);
780 env(ticket::create(alice, 2));
783 env.require(Owners(alice, 2), tickets(alice, 2));
784 BEAST_EXPECT(ticketSeq + 2 == env.seq(alice));
793 tx[jss::tx_json] = noop(alice);
794 tx[jss::tx_json][sfTicketSequence.jsonName] = ticketSeq;
798 BEAST_EXPECT(!tx[jss::tx_json].isMember(sfSequence.jsonName));
805 if (BEAST_EXPECT(jr[jss::result][jss::tx_json].isMember(sfSequence.jsonName)))
807 BEAST_EXPECT(jr[jss::result][jss::tx_json][sfSequence.jsonName] == 0);
812 env.require(Owners(alice, 2), tickets(alice, 2));
815 env.rpc(
"submit", jr[jss::result][jss::tx_blob].asString());
817 env.require(Owners(alice, 1), tickets(alice, 1));
826 tx[jss::tx_json] = noop(alice);
827 tx[jss::tx_json][sfTicketSequence.jsonName] = ticketSeq + 1;
831 BEAST_EXPECT(!tx[jss::tx_json].isMember(sfSequence.jsonName));
838 if (BEAST_EXPECT(jr[jss::result][jss::tx_json].isMember(sfSequence.jsonName)))
840 BEAST_EXPECT(jr[jss::result][jss::tx_json][sfSequence.jsonName] == 0);
845 env.require(Owners(alice, 0), tickets(alice, 0));
856 testcase(
"Fix both Seq and Ticket");
858 Env env{*
this, testableAmendments()};
861 env.fund(XRP(10000), alice);
866 env(ticket::create(alice, 1));
868 env.require(Owners(alice, 1), tickets(alice, 1));
869 BEAST_EXPECT(ticketSeq + 1 == env.seq(alice));
873 env(noop(alice), ticket::Use(ticketSeq), Seq(env.seq(alice)), Ter(
temSEQ_AND_TICKET));
878 env.require(Owners(alice, 1), tickets(alice, 1));
879 BEAST_EXPECT(ticketSeq + 1 == env.seq(alice));