18 using namespace std::string_literals;
22 std::string const txType = tx[sfTransactionType.jsonName].asString();
25 txType == jss::TicketCreate,
"Unexpected TransactionType: "s + txType))
29 std::uint32_t const count = {tx[sfTicketCount.jsonName].asUInt()};
30 if (!BEAST_EXPECTS(count >= 1,
"Unexpected ticket count: "s +
std::to_string(count)))
33 std::uint32_t const txSeq = {tx[sfSequence.jsonName].asUInt()};
34 std::string const account = tx[sfAccount.jsonName].asString();
38 metadata.
isMember(sfTransactionResult.jsonName) &&
39 metadata[sfTransactionResult.jsonName].
asString() ==
"tesSUCCESS",
40 "Not metadata for successful TicketCreate."))
43 BEAST_EXPECT(metadata.
isMember(sfAffectedNodes.jsonName));
44 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].
isArray());
46 bool directoryChanged =
false;
50 for (
Json::Value const& node : metadata[sfAffectedNodes.jsonName])
52 if (node.isMember(sfModifiedNode.jsonName))
54 Json::Value const& modified = node[sfModifiedNode.jsonName];
56 if (entryType == jss::AccountRoot)
58 auto const& previousFields = modified[sfPreviousFields.jsonName];
59 auto const& finalFields = modified[sfFinalFields.jsonName];
62 std::uint32_t const prevSeq = previousFields[sfSequence.jsonName].asUInt();
64 acctRootFinalSeq = finalFields[sfSequence.jsonName].asUInt();
69 BEAST_EXPECT(acctRootFinalSeq == prevSeq + count);
74 BEAST_EXPECT(prevSeq == txSeq);
75 BEAST_EXPECT(acctRootFinalSeq == prevSeq + count + 1);
79 std::uint32_t const consumedTickets = {txSeq == 0u ? 1u : 0u};
88 bool const unreportedPrevTicketCount = {count == 1 && txSeq == 0};
91 if (unreportedPrevTicketCount)
95 BEAST_EXPECT(!previousFields.isMember(sfOwnerCount.jsonName));
101 previousFields[sfOwnerCount.jsonName].asUInt()};
104 finalFields[sfOwnerCount.jsonName].asUInt()};
106 BEAST_EXPECT(prevCount + count - consumedTickets == finalCount);
110 BEAST_EXPECT(finalFields.isMember(sfTicketCount.jsonName));
112 if (unreportedPrevTicketCount)
116 BEAST_EXPECT(!previousFields.isMember(sfTicketCount.jsonName));
123 previousFields.isMember(sfTicketCount.jsonName)
124 ? previousFields[sfTicketCount.jsonName].asUInt()
128 (startCount == 0u) ^ previousFields.isMember(sfTicketCount.jsonName));
131 startCount + count - consumedTickets ==
132 finalFields[sfTicketCount.jsonName]);
135 else if (entryType == jss::DirectoryNode)
137 directoryChanged =
true;
141 fail(
"Unexpected modified node: "s + entryType, __FILE__, __LINE__);
144 else if (node.isMember(sfCreatedNode.jsonName))
146 Json::Value const& created = node[sfCreatedNode.jsonName];
148 if (entryType == jss::Ticket)
150 auto const& newFields = created[sfNewFields.jsonName];
152 BEAST_EXPECT(newFields[sfAccount.jsonName].asString() == account);
153 ticketSeqs.
push_back(newFields[sfTicketSequence.jsonName].asUInt());
155 else if (entryType == jss::DirectoryNode)
157 directoryChanged =
true;
161 fail(
"Unexpected created node: "s + entryType, __FILE__, __LINE__);
164 else if (node.isMember(sfDeletedNode.jsonName))
166 Json::Value const& deleted = node[sfDeletedNode.jsonName];
167 std::string const entryType = deleted[sfLedgerEntryType.jsonName].asString();
169 if (entryType == jss::Ticket)
172 BEAST_EXPECT(txSeq == 0);
175 auto const& finalFields = deleted[sfFinalFields.jsonName];
176 BEAST_EXPECT(finalFields[sfAccount.jsonName].asString() == account);
180 finalFields[sfTicketSequence.jsonName].asUInt() ==
181 tx[sfTicketSequence.jsonName].asUInt());
186 fail(
"Unexpected node type in TicketCreate metadata.", __FILE__, __LINE__);
189 BEAST_EXPECT(directoryChanged);
192 BEAST_EXPECT(ticketSeqs.
size() == count);
195 BEAST_EXPECT(*ticketSeqs.
rbegin() == acctRootFinalSeq - 1);
230 BEAST_EXPECT(tx[sfSequence.jsonName].asUInt() == 0);
231 std::string const account{tx[sfAccount.jsonName].asString()};
233 tx.isMember(sfTicketSequence.jsonName),
234 "Not metadata for a ticket consuming transaction."))
237 std::uint32_t const ticketSeq{tx[sfTicketSequence.jsonName].asUInt()};
241 metadata.isMember(sfTransactionResult.jsonName),
242 "Metadata is missing TransactionResult."))
246 std::string const transactionResult{metadata[sfTransactionResult.jsonName].asString()};
248 transactionResult ==
"tesSUCCESS" ||
249 transactionResult.compare(0, 3,
"tec") == 0,
250 transactionResult +
" neither tesSUCCESS nor tec"))
254 BEAST_EXPECT(metadata.isMember(sfAffectedNodes.jsonName));
255 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].isArray());
257 bool acctRootFound{
false};
259 int ticketsRemoved{0};
260 for (
Json::Value const& node : metadata[sfAffectedNodes.jsonName])
262 if (node.isMember(sfModifiedNode.jsonName))
264 Json::Value const& modified{node[sfModifiedNode.jsonName]};
265 std::string const entryType = modified[sfLedgerEntryType.jsonName].asString();
266 if (entryType ==
"AccountRoot" &&
267 modified[sfFinalFields.jsonName][sfAccount.jsonName].asString() == account)
269 acctRootFound =
true;
271 auto const& previousFields = modified[sfPreviousFields.jsonName];
272 auto const& finalFields = modified[sfFinalFields.jsonName];
274 acctRootSeq = finalFields[sfSequence.jsonName].asUInt();
280 previousFields.isMember(sfTicketCount.jsonName),
281 "AccountRoot previous is missing TicketCount"))
285 previousFields[sfTicketCount.jsonName].asUInt();
287 BEAST_EXPECT(prevTicketCount > 0);
288 if (prevTicketCount == 1)
290 BEAST_EXPECT(!finalFields.isMember(sfTicketCount.jsonName));
295 finalFields.isMember(sfTicketCount.jsonName) &&
296 finalFields[sfTicketCount.jsonName].asUInt() == prevTicketCount - 1);
300 else if (node.isMember(sfDeletedNode.jsonName))
302 Json::Value const& deleted{node[sfDeletedNode.jsonName]};
303 std::string const entryType{deleted[sfLedgerEntryType.jsonName].asString()};
305 if (entryType == jss::Ticket)
309 deleted[sfFinalFields.jsonName][sfAccount.jsonName].asString() == account);
313 deleted[sfFinalFields.jsonName][sfTicketSequence.jsonName].asUInt() ==
320 BEAST_EXPECT(acctRootFound);
321 BEAST_EXPECT(ticketsRemoved == 1);
322 BEAST_EXPECT(ticketSeq < acctRootSeq);
328 testcase(
"Create Tickets that fail Preflight");
330 using namespace test::jtx;
333 Account
const master{env.master};
341 env(ticket::create(master, 1), fee(XRP(10)));
344 env.require(owners(master, 1), tickets(master, 1));
346 env(ticket::create(master, 1), fee(XRP(-1)), ter(
temBAD_FEE));
353 env.require(owners(master, 2), tickets(master, 2));
357 env.require(owners(master, 2), tickets(master, 2));
361 env(
noop(master), ticket::use(ticketSeq_A));
364 env.require(owners(master, 1), tickets(master, 1));
366 env(ticket::create(master, 250), ticket::use(ticketSeq_B));
369 env.require(owners(master, 250), tickets(master, 250));
375 testcase(
"Create Tickets that fail Preclaim");
377 using namespace test::jtx;
381 Account
const alice{
"alice"};
384 env(ticket::create(alice, 1), json(jss::Sequence, 1), ter(
terNO_ACCOUNT));
390 Account
const alice{
"alice"};
392 env.fund(XRP(100000), alice);
395 env(ticket::create(alice, 250));
398 env.require(owners(alice, 250), tickets(alice, 250));
402 env(ticket::create(alice, 1), ticket::use(ticketSeq + 0));
405 env.require(owners(alice, 250), tickets(alice, 250));
408 env(ticket::create(alice, 2), ticket::use(ticketSeq + 1), ter(
tecDIR_FULL));
410 env.require(owners(alice, 249), tickets(alice, 249));
413 env(ticket::create(alice, 2), ticket::use(ticketSeq + 2));
416 env.require(owners(alice, 250), tickets(alice, 250));
422 env.require(owners(alice, 250), tickets(alice, 250));
427 Account
const alice{
"alice"};
429 env.fund(XRP(100000), alice);
433 env(ticket::create(alice, 2));
436 env.require(owners(alice, 2), tickets(alice, 2));
440 env(ticket::create(alice, 250), ticket::use(ticketSeq_AB + 0), ter(
tecDIR_FULL));
442 env.require(owners(alice, 1), tickets(alice, 1));
448 env.require(owners(alice, 1), tickets(alice, 1));
451 env(ticket::create(alice, 250), ticket::use(ticketSeq_AB + 1));
454 env.require(owners(alice, 250), tickets(alice, 250));
461 testcase(
"Create Ticket Insufficient Reserve");
463 using namespace test::jtx;
465 Account
const alice{
"alice"};
468 env.fund(env.current()->fees().accountReserve(1) - drops(1), alice);
473 env.require(owners(alice, 0), tickets(alice, 0));
476 env(pay(env.master, alice, env.current()->fees().accountReserve(1) - env.balance(alice)));
479 env(ticket::create(alice, 1));
482 env.require(owners(alice, 1), tickets(alice, 1));
489 env.current()->fees().accountReserve(250) - drops(1) - env.balance(alice)));
496 env.require(owners(alice, 1), tickets(alice, 1));
500 env(pay(env.master, alice, env.current()->fees().accountReserve(250) - env.balance(alice)));
504 env(ticket::create(alice, 249));
507 env.require(owners(alice, 250), tickets(alice, 250));
508 BEAST_EXPECT(ticketSeq + 249 == env.seq(alice));
516 using namespace test::jtx;
518 Account
const alice{
"alice"};
520 env.fund(XRP(10000), alice);
525 env(ticket::create(alice, 2));
528 env.require(owners(alice, 2), tickets(alice, 2));
529 BEAST_EXPECT(ticketSeq_AB + 2 == env.seq(alice));
533 env(ticket::create(alice, 1), ticket::use(ticketSeq_AB + 0));
536 env.require(owners(alice, 2), tickets(alice, 2));
537 BEAST_EXPECT(ticketSeq_C + 1 == env.seq(alice));
541 env(ticket::create(alice, 2), ticket::use(ticketSeq_AB + 1));
544 env.require(owners(alice, 3), tickets(alice, 3));
545 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
548 env(
noop(alice), ticket::use(ticketSeq_DE + 0));
551 env.require(owners(alice, 2), tickets(alice, 2));
552 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
554 env(pay(alice, env.master, XRP(20)), ticket::use(ticketSeq_DE + 1));
557 env.require(owners(alice, 1), tickets(alice, 1));
558 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
560 env(trust(alice, env.master[
"USD"](20)), ticket::use(ticketSeq_C));
563 env.require(owners(alice, 1), tickets(alice, 0));
564 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
576 env(ticket::create(alice, 1));
579 env.require(owners(alice, 1), tickets(alice, 0));
580 BEAST_EXPECT(ticketSeq_F + 1 == env.seq(alice));
585 env(ticket::create(alice, 1));
590 ticket::use(ticketSeq_G),
591 json(R
"({"AccountTxnID": "0"})"),
594 env.require(owners(alice, 2), tickets(alice, 1));
610 testcase(
"Transaction Database With Tickets");
612 using namespace test::jtx;
614 Account
const alice{
"alice"};
616 env.fund(XRP(10000), alice);
620 auto getTxID = [&env,
this]() ->
uint256 {
622 if (!BEAST_EXPECTS(tx,
"Transaction not found"))
623 Throw<std::invalid_argument>(
"Invalid transaction ID");
625 return tx->getTransactionID();
639 env(ticket::create(alice, ticketCount));
640 uint256 const txHash_1{getTxID()};
643 ticketSeq += ticketCount;
644 env(
noop(alice), ticket::use(--ticketSeq));
645 uint256 const txHash_2{getTxID()};
647 env(pay(alice, env.master, XRP(200)), ticket::use(--ticketSeq));
648 uint256 const txHash_3{getTxID()};
650 env(deposit::auth(alice, env.master), ticket::use(--ticketSeq));
651 uint256 const txHash_4{getTxID()};
657 env(pay(alice, env.master, XRP(300)), ticket::use(--ticketSeq));
658 uint256 const txHash_5{getTxID()};
660 env(pay(alice, env.master, XRP(400)), ticket::use(--ticketSeq));
661 uint256 const txHash_6{getTxID()};
663 env(deposit::unauth(alice, env.master), ticket::use(--ticketSeq));
664 uint256 const txHash_7{getTxID()};
666 env(
noop(alice), ticket::use(--ticketSeq));
667 uint256 const txHash_8{getTxID()};
677 auto checkTxFromDB = [&env,
this](
693 BEAST_EXPECT(tx->getLedger() == ledgerSeq);
695 BEAST_EXPECT((*sttx)[sfSequence] == txSeq);
697 BEAST_EXPECT((*sttx)[sfTicketSequence] == *ticketSeq);
698 BEAST_EXPECT((*sttx)[sfTransactionType] == txType);
702 fail(
"Expected transaction was not found");
707 checkTxFromDB(txHash_1, 4, 4, {}, ttTICKET_CREATE);
708 checkTxFromDB(txHash_2, 4, 0, 13, ttACCOUNT_SET);
709 checkTxFromDB(txHash_3, 4, 0, 12, ttPAYMENT);
710 checkTxFromDB(txHash_4, 4, 0, 11, ttDEPOSIT_PREAUTH);
712 checkTxFromDB(txHash_5, 5, 0, 10, ttPAYMENT);
713 checkTxFromDB(txHash_6, 5, 0, 9, ttPAYMENT);
714 checkTxFromDB(txHash_7, 5, 0, 8, ttDEPOSIT_PREAUTH);
715 checkTxFromDB(txHash_8, 5, 0, 7, ttACCOUNT_SET);
725 testcase(
"Sign with TicketSequence");
727 using namespace test::jtx;
729 Account
const alice{
"alice"};
731 env.fund(XRP(10000), alice);
736 env(ticket::create(alice, 2));
739 env.require(owners(alice, 2), tickets(alice, 2));
740 BEAST_EXPECT(ticketSeq + 2 == env.seq(alice));
749 tx[jss::tx_json] =
noop(alice);
750 tx[jss::tx_json][sfTicketSequence.jsonName] = ticketSeq;
754 BEAST_EXPECT(!tx[jss::tx_json].isMember(sfSequence.jsonName));
761 if (BEAST_EXPECT(jr[jss::result][jss::tx_json].isMember(sfSequence.jsonName)))
763 BEAST_EXPECT(jr[jss::result][jss::tx_json][sfSequence.jsonName] == 0);
768 env.require(owners(alice, 2), tickets(alice, 2));
771 env.rpc(
"submit", jr[jss::result][jss::tx_blob].asString());
773 env.require(owners(alice, 1), tickets(alice, 1));
782 tx[jss::tx_json] =
noop(alice);
783 tx[jss::tx_json][sfTicketSequence.jsonName] = ticketSeq + 1;
787 BEAST_EXPECT(!tx[jss::tx_json].isMember(sfSequence.jsonName));
794 if (BEAST_EXPECT(jr[jss::result][jss::tx_json].isMember(sfSequence.jsonName)))
796 BEAST_EXPECT(jr[jss::result][jss::tx_json][sfSequence.jsonName] == 0);
801 env.require(owners(alice, 0), tickets(alice, 0));
808 using namespace test::jtx;
812 testcase(
"Fix both Seq and Ticket");
814 Env env{*
this, testable_amendments()};
815 Account
const alice{
"alice"};
817 env.fund(XRP(10000), alice);
822 env(ticket::create(alice, 1));
824 env.require(owners(alice, 1), tickets(alice, 1));
825 BEAST_EXPECT(ticketSeq + 1 == env.seq(alice));
834 env.require(owners(alice, 1), tickets(alice, 1));
835 BEAST_EXPECT(ticketSeq + 1 == env.seq(alice));