18 using namespace std::string_literals;
22 std::string const txType = tx[sfTransactionType.jsonName].asString();
24 if (!BEAST_EXPECTS(txType == jss::TicketCreate,
"Unexpected TransactionType: "s + txType))
28 std::uint32_t const count = {tx[sfTicketCount.jsonName].asUInt()};
29 if (!BEAST_EXPECTS(count >= 1,
"Unexpected ticket count: "s +
std::to_string(count)))
32 std::uint32_t const txSeq = {tx[sfSequence.jsonName].asUInt()};
33 std::string const account = tx[sfAccount.jsonName].asString();
37 metadata.
isMember(sfTransactionResult.jsonName) &&
38 metadata[sfTransactionResult.jsonName].
asString() ==
"tesSUCCESS",
39 "Not metadata for successful TicketCreate."))
42 BEAST_EXPECT(metadata.
isMember(sfAffectedNodes.jsonName));
43 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].
isArray());
45 bool directoryChanged =
false;
49 for (
Json::Value const& node : metadata[sfAffectedNodes.jsonName])
51 if (node.isMember(sfModifiedNode.jsonName))
53 Json::Value const& modified = node[sfModifiedNode.jsonName];
55 if (entryType == jss::AccountRoot)
57 auto const& previousFields = modified[sfPreviousFields.jsonName];
58 auto const& finalFields = modified[sfFinalFields.jsonName];
61 std::uint32_t const prevSeq = previousFields[sfSequence.jsonName].asUInt();
63 acctRootFinalSeq = finalFields[sfSequence.jsonName].asUInt();
68 BEAST_EXPECT(acctRootFinalSeq == prevSeq + count);
73 BEAST_EXPECT(prevSeq == txSeq);
74 BEAST_EXPECT(acctRootFinalSeq == prevSeq + count + 1);
78 std::uint32_t const consumedTickets = {txSeq == 0u ? 1u : 0u};
87 bool const unreportedPrevTicketCount = {count == 1 && txSeq == 0};
90 if (unreportedPrevTicketCount)
94 BEAST_EXPECT(!previousFields.isMember(sfOwnerCount.jsonName));
99 std::uint32_t const prevCount = {previousFields[sfOwnerCount.jsonName].asUInt()};
101 std::uint32_t const finalCount = {finalFields[sfOwnerCount.jsonName].asUInt()};
103 BEAST_EXPECT(prevCount + count - consumedTickets == finalCount);
107 BEAST_EXPECT(finalFields.isMember(sfTicketCount.jsonName));
109 if (unreportedPrevTicketCount)
113 BEAST_EXPECT(!previousFields.isMember(sfTicketCount.jsonName));
120 previousFields.isMember(sfTicketCount.jsonName)
121 ? previousFields[sfTicketCount.jsonName].asUInt()
124 BEAST_EXPECT((startCount == 0u) ^ previousFields.isMember(sfTicketCount.jsonName));
126 BEAST_EXPECT(startCount + count - consumedTickets == finalFields[sfTicketCount.jsonName]);
129 else if (entryType == jss::DirectoryNode)
131 directoryChanged =
true;
135 fail(
"Unexpected modified node: "s + entryType, __FILE__, __LINE__);
138 else if (node.isMember(sfCreatedNode.jsonName))
140 Json::Value const& created = node[sfCreatedNode.jsonName];
142 if (entryType == jss::Ticket)
144 auto const& newFields = created[sfNewFields.jsonName];
146 BEAST_EXPECT(newFields[sfAccount.jsonName].asString() == account);
147 ticketSeqs.
push_back(newFields[sfTicketSequence.jsonName].asUInt());
149 else if (entryType == jss::DirectoryNode)
151 directoryChanged =
true;
155 fail(
"Unexpected created node: "s + entryType, __FILE__, __LINE__);
158 else if (node.isMember(sfDeletedNode.jsonName))
160 Json::Value const& deleted = node[sfDeletedNode.jsonName];
161 std::string const entryType = deleted[sfLedgerEntryType.jsonName].asString();
163 if (entryType == jss::Ticket)
166 BEAST_EXPECT(txSeq == 0);
169 auto const& finalFields = deleted[sfFinalFields.jsonName];
170 BEAST_EXPECT(finalFields[sfAccount.jsonName].asString() == account);
174 finalFields[sfTicketSequence.jsonName].asUInt() == tx[sfTicketSequence.jsonName].asUInt());
179 fail(
"Unexpected node type in TicketCreate metadata.", __FILE__, __LINE__);
182 BEAST_EXPECT(directoryChanged);
185 BEAST_EXPECT(ticketSeqs.
size() == count);
188 BEAST_EXPECT(*ticketSeqs.
rbegin() == acctRootFinalSeq - 1);
223 BEAST_EXPECT(tx[sfSequence.jsonName].asUInt() == 0);
224 std::string const account{tx[sfAccount.jsonName].asString()};
225 if (!BEAST_EXPECTS(tx.isMember(sfTicketSequence.jsonName),
"Not metadata for a ticket consuming transaction."))
228 std::uint32_t const ticketSeq{tx[sfTicketSequence.jsonName].asUInt()};
231 if (!BEAST_EXPECTS(metadata.isMember(sfTransactionResult.jsonName),
"Metadata is missing TransactionResult."))
235 std::string const transactionResult{metadata[sfTransactionResult.jsonName].asString()};
237 transactionResult ==
"tesSUCCESS" || transactionResult.compare(0, 3,
"tec") == 0,
238 transactionResult +
" neither tesSUCCESS nor tec"))
242 BEAST_EXPECT(metadata.isMember(sfAffectedNodes.jsonName));
243 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].isArray());
245 bool acctRootFound{
false};
247 int ticketsRemoved{0};
248 for (
Json::Value const& node : metadata[sfAffectedNodes.jsonName])
250 if (node.isMember(sfModifiedNode.jsonName))
252 Json::Value const& modified{node[sfModifiedNode.jsonName]};
253 std::string const entryType = modified[sfLedgerEntryType.jsonName].asString();
254 if (entryType ==
"AccountRoot" &&
255 modified[sfFinalFields.jsonName][sfAccount.jsonName].asString() == account)
257 acctRootFound =
true;
259 auto const& previousFields = modified[sfPreviousFields.jsonName];
260 auto const& finalFields = modified[sfFinalFields.jsonName];
262 acctRootSeq = finalFields[sfSequence.jsonName].asUInt();
268 previousFields.isMember(sfTicketCount.jsonName),
269 "AccountRoot previous is missing TicketCount"))
272 std::uint32_t const prevTicketCount = previousFields[sfTicketCount.jsonName].asUInt();
274 BEAST_EXPECT(prevTicketCount > 0);
275 if (prevTicketCount == 1)
276 BEAST_EXPECT(!finalFields.isMember(sfTicketCount.jsonName));
279 finalFields.isMember(sfTicketCount.jsonName) &&
280 finalFields[sfTicketCount.jsonName].asUInt() == prevTicketCount - 1);
283 else if (node.isMember(sfDeletedNode.jsonName))
285 Json::Value const& deleted{node[sfDeletedNode.jsonName]};
286 std::string const entryType{deleted[sfLedgerEntryType.jsonName].asString()};
288 if (entryType == jss::Ticket)
291 BEAST_EXPECT(deleted[sfFinalFields.jsonName][sfAccount.jsonName].asString() == account);
294 BEAST_EXPECT(deleted[sfFinalFields.jsonName][sfTicketSequence.jsonName].asUInt() == ticketSeq);
300 BEAST_EXPECT(acctRootFound);
301 BEAST_EXPECT(ticketsRemoved == 1);
302 BEAST_EXPECT(ticketSeq < acctRootSeq);
308 testcase(
"Create Tickets that fail Preflight");
310 using namespace test::jtx;
313 Account
const master{env.master};
321 env(ticket::create(master, 1), fee(XRP(10)));
324 env.require(owners(master, 1), tickets(master, 1));
326 env(ticket::create(master, 1), fee(XRP(-1)), ter(
temBAD_FEE));
333 env.require(owners(master, 2), tickets(master, 2));
337 env.require(owners(master, 2), tickets(master, 2));
341 env(
noop(master), ticket::use(ticketSeq_A));
344 env.require(owners(master, 1), tickets(master, 1));
346 env(ticket::create(master, 250), ticket::use(ticketSeq_B));
349 env.require(owners(master, 250), tickets(master, 250));
355 testcase(
"Create Tickets that fail Preclaim");
357 using namespace test::jtx;
361 Account alice{
"alice"};
364 env(ticket::create(alice, 1), json(jss::Sequence, 1), ter(
terNO_ACCOUNT));
370 Account alice{
"alice"};
372 env.fund(XRP(100000), alice);
375 env(ticket::create(alice, 250));
378 env.require(owners(alice, 250), tickets(alice, 250));
382 env(ticket::create(alice, 1), ticket::use(ticketSeq + 0));
385 env.require(owners(alice, 250), tickets(alice, 250));
388 env(ticket::create(alice, 2), ticket::use(ticketSeq + 1), ter(
tecDIR_FULL));
390 env.require(owners(alice, 249), tickets(alice, 249));
393 env(ticket::create(alice, 2), ticket::use(ticketSeq + 2));
396 env.require(owners(alice, 250), tickets(alice, 250));
402 env.require(owners(alice, 250), tickets(alice, 250));
407 Account alice{
"alice"};
409 env.fund(XRP(100000), alice);
413 env(ticket::create(alice, 2));
416 env.require(owners(alice, 2), tickets(alice, 2));
420 env(ticket::create(alice, 250), ticket::use(ticketSeq_AB + 0), ter(
tecDIR_FULL));
422 env.require(owners(alice, 1), tickets(alice, 1));
428 env.require(owners(alice, 1), tickets(alice, 1));
431 env(ticket::create(alice, 250), ticket::use(ticketSeq_AB + 1));
434 env.require(owners(alice, 250), tickets(alice, 250));
441 testcase(
"Create Ticket Insufficient Reserve");
443 using namespace test::jtx;
445 Account alice{
"alice"};
448 env.fund(env.current()->fees().accountReserve(1) - drops(1), alice);
453 env.require(owners(alice, 0), tickets(alice, 0));
456 env(pay(env.master, alice, env.current()->fees().accountReserve(1) - env.balance(alice)));
459 env(ticket::create(alice, 1));
462 env.require(owners(alice, 1), tickets(alice, 1));
466 env(pay(env.master, alice, env.current()->fees().accountReserve(250) - drops(1) - env.balance(alice)));
473 env.require(owners(alice, 1), tickets(alice, 1));
477 env(pay(env.master, alice, env.current()->fees().accountReserve(250) - env.balance(alice)));
481 env(ticket::create(alice, 249));
484 env.require(owners(alice, 250), tickets(alice, 250));
485 BEAST_EXPECT(ticketSeq + 249 == env.seq(alice));
493 using namespace test::jtx;
495 Account alice{
"alice"};
497 env.fund(XRP(10000), alice);
502 env(ticket::create(alice, 2));
505 env.require(owners(alice, 2), tickets(alice, 2));
506 BEAST_EXPECT(ticketSeq_AB + 2 == env.seq(alice));
510 env(ticket::create(alice, 1), ticket::use(ticketSeq_AB + 0));
513 env.require(owners(alice, 2), tickets(alice, 2));
514 BEAST_EXPECT(ticketSeq_C + 1 == env.seq(alice));
518 env(ticket::create(alice, 2), ticket::use(ticketSeq_AB + 1));
521 env.require(owners(alice, 3), tickets(alice, 3));
522 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
525 env(
noop(alice), ticket::use(ticketSeq_DE + 0));
528 env.require(owners(alice, 2), tickets(alice, 2));
529 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
531 env(pay(alice, env.master, XRP(20)), ticket::use(ticketSeq_DE + 1));
534 env.require(owners(alice, 1), tickets(alice, 1));
535 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
537 env(trust(alice, env.master[
"USD"](20)), ticket::use(ticketSeq_C));
540 env.require(owners(alice, 1), tickets(alice, 0));
541 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
553 env(ticket::create(alice, 1));
556 env.require(owners(alice, 1), tickets(alice, 0));
557 BEAST_EXPECT(ticketSeq_F + 1 == env.seq(alice));
562 env(ticket::create(alice, 1));
566 env(
noop(alice), ticket::use(ticketSeq_G), json(R
"({"AccountTxnID": "0"})"), ter(temINVALID));
568 env.require(owners(alice, 2), tickets(alice, 1));
584 testcase(
"Transaction Database With Tickets");
586 using namespace test::jtx;
588 Account alice{
"alice"};
590 env.fund(XRP(10000), alice);
594 auto getTxID = [&env,
this]() ->
uint256 {
596 if (!BEAST_EXPECTS(tx,
"Transaction not found"))
597 Throw<std::invalid_argument>(
"Invalid transaction ID");
599 return tx->getTransactionID();
613 env(ticket::create(alice, ticketCount));
614 uint256 const txHash_1{getTxID()};
617 ticketSeq += ticketCount;
618 env(
noop(alice), ticket::use(--ticketSeq));
619 uint256 const txHash_2{getTxID()};
621 env(pay(alice, env.master, XRP(200)), ticket::use(--ticketSeq));
622 uint256 const txHash_3{getTxID()};
624 env(deposit::auth(alice, env.master), ticket::use(--ticketSeq));
625 uint256 const txHash_4{getTxID()};
631 env(pay(alice, env.master, XRP(300)), ticket::use(--ticketSeq));
632 uint256 const txHash_5{getTxID()};
634 env(pay(alice, env.master, XRP(400)), ticket::use(--ticketSeq));
635 uint256 const txHash_6{getTxID()};
637 env(deposit::unauth(alice, env.master), ticket::use(--ticketSeq));
638 uint256 const txHash_7{getTxID()};
640 env(
noop(alice), ticket::use(--ticketSeq));
641 uint256 const txHash_8{getTxID()};
651 auto checkTxFromDB = [&env,
this](
666 BEAST_EXPECT(tx->getLedger() == ledgerSeq);
668 BEAST_EXPECT((*sttx)[sfSequence] == txSeq);
670 BEAST_EXPECT((*sttx)[sfTicketSequence] == *ticketSeq);
671 BEAST_EXPECT((*sttx)[sfTransactionType] == txType);
675 fail(
"Expected transaction was not found");
680 checkTxFromDB(txHash_1, 4, 4, {}, ttTICKET_CREATE);
681 checkTxFromDB(txHash_2, 4, 0, 13, ttACCOUNT_SET);
682 checkTxFromDB(txHash_3, 4, 0, 12, ttPAYMENT);
683 checkTxFromDB(txHash_4, 4, 0, 11, ttDEPOSIT_PREAUTH);
685 checkTxFromDB(txHash_5, 5, 0, 10, ttPAYMENT);
686 checkTxFromDB(txHash_6, 5, 0, 9, ttPAYMENT);
687 checkTxFromDB(txHash_7, 5, 0, 8, ttDEPOSIT_PREAUTH);
688 checkTxFromDB(txHash_8, 5, 0, 7, ttACCOUNT_SET);
698 testcase(
"Sign with TicketSequence");
700 using namespace test::jtx;
702 Account alice{
"alice"};
704 env.fund(XRP(10000), alice);
709 env(ticket::create(alice, 2));
712 env.require(owners(alice, 2), tickets(alice, 2));
713 BEAST_EXPECT(ticketSeq + 2 == env.seq(alice));
722 tx[jss::tx_json] =
noop(alice);
723 tx[jss::tx_json][sfTicketSequence.jsonName] = ticketSeq;
727 BEAST_EXPECT(!tx[jss::tx_json].isMember(sfSequence.jsonName));
734 if (BEAST_EXPECT(jr[jss::result][jss::tx_json].isMember(sfSequence.jsonName)))
736 BEAST_EXPECT(jr[jss::result][jss::tx_json][sfSequence.jsonName] == 0);
741 env.require(owners(alice, 2), tickets(alice, 2));
744 env.rpc(
"submit", jr[jss::result][jss::tx_blob].asString());
746 env.require(owners(alice, 1), tickets(alice, 1));
755 tx[jss::tx_json] =
noop(alice);
756 tx[jss::tx_json][sfTicketSequence.jsonName] = ticketSeq + 1;
760 BEAST_EXPECT(!tx[jss::tx_json].isMember(sfSequence.jsonName));
767 if (BEAST_EXPECT(jr[jss::result][jss::tx_json].isMember(sfSequence.jsonName)))
769 BEAST_EXPECT(jr[jss::result][jss::tx_json][sfSequence.jsonName] == 0);
774 env.require(owners(alice, 0), tickets(alice, 0));
781 using namespace test::jtx;
785 testcase(
"Fix both Seq and Ticket");
787 Env env{*
this, testable_amendments()};
788 Account alice{
"alice"};
790 env.fund(XRP(10000), alice);
795 env(ticket::create(alice, 1));
797 env.require(owners(alice, 1), tickets(alice, 1));
798 BEAST_EXPECT(ticketSeq + 1 == env.seq(alice));
807 env.require(owners(alice, 1), tickets(alice, 1));
808 BEAST_EXPECT(ticketSeq + 1 == env.seq(alice));