rippled
Loading...
Searching...
No Matches
LedgerEntry_test.cpp
1#include <test/jtx.h>
2#include <test/jtx/Oracle.h>
3#include <test/jtx/attester.h>
4#include <test/jtx/delegate.h>
5#include <test/jtx/multisign.h>
6#include <test/jtx/xchain_bridge.h>
7
8#include <xrpld/app/tx/apply.h>
9
10#include <xrpl/beast/unit_test.h>
11#include <xrpl/json/json_value.h>
12#include <xrpl/protocol/AccountID.h>
13#include <xrpl/protocol/ErrorCodes.h>
14#include <xrpl/protocol/STXChainBridge.h>
15#include <xrpl/protocol/jss.h>
16
17#include <source_location>
18namespace xrpl {
19
20namespace test {
21
37
39 {jss::account, FieldType::AccountField},
40 {jss::accounts, FieldType::TwoAccountArrayField},
41 {jss::asset, FieldType::IssueField},
42 {jss::asset2, FieldType::IssueField},
43 {jss::authorize, FieldType::AccountField},
44 {jss::authorized, FieldType::AccountField},
45 {jss::credential_type, FieldType::BlobField},
46 {jss::currency, FieldType::CurrencyField},
47 {jss::issuer, FieldType::AccountField},
48 {jss::oracle_document_id, FieldType::UInt32Field},
49 {jss::owner, FieldType::AccountField},
50 {jss::seq, FieldType::UInt32Field},
51 {jss::subject, FieldType::AccountField},
52 {jss::ticket_seq, FieldType::UInt32Field},
53};
54
57{
58 auto it = std::ranges::find_if(mappings, [&fieldName](auto const& pair) {
59 return pair.first == fieldName;
60 });
61 if (it != mappings.end())
62 {
63 return it->second;
64 }
65 else
66 {
67 Throw<std::runtime_error>(
68 "`mappings` is missing field " + std::string(fieldName.c_str()));
69 }
70}
71
74{
75 switch (typeID)
76 {
78 return "AccountID";
80 return "array";
82 return "hex string";
84 return "Currency";
87 return "hex string";
89 return "hex string or object";
91 return "Issue";
93 return "length-2 array of Accounts";
95 return "number";
97 return "number";
98 default:
99 Throw<std::runtime_error>(
100 "unknown type " + std::to_string(static_cast<uint8_t>(typeID)));
101 }
102}
103
105{
106 void
108 Json::Value const& jv,
109 std::string const& err,
110 std::string const& msg,
112 {
113 if (BEAST_EXPECT(jv.isMember(jss::status)))
114 BEAST_EXPECTS(
115 jv[jss::status] == "error", std::to_string(location.line()));
116 if (BEAST_EXPECT(jv.isMember(jss::error)))
117 BEAST_EXPECTS(
118 jv[jss::error] == err,
119 "Expected error " + err + ", received " +
120 jv[jss::error].asString() + ", at line " +
121 std::to_string(location.line()) + ", " +
122 jv.toStyledString());
123 if (msg.empty())
124 {
125 BEAST_EXPECTS(
126 jv[jss::error_message] == Json::nullValue ||
127 jv[jss::error_message] == "",
128 "Expected no error message, received \"" +
129 jv[jss::error_message].asString() + "\", at line " +
130 std::to_string(location.line()) + ", " +
131 jv.toStyledString());
132 }
133 else if (BEAST_EXPECT(jv.isMember(jss::error_message)))
134 BEAST_EXPECTS(
135 jv[jss::error_message] == msg,
136 "Expected error message \"" + msg + "\", received \"" +
137 jv[jss::error_message].asString() + "\", at line " +
138 std::to_string(location.line()) + ", " +
139 jv.toStyledString());
140 }
141
144 {
145 static Json::Value const injectObject = []() {
147 obj[jss::account] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
148 obj[jss::ledger_index] = "validated";
149 return obj;
150 }();
151 static Json::Value const injectArray = []() {
153 arr[0u] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
154 arr[1u] = "validated";
155 return arr;
156 }();
157 static std::array<Json::Value, 21> const allBadValues = {
158 "", // 0
159 true, // 1
160 1, // 2
161 "1", // 3
162 -1, // 4
163 1.1, // 5
164 "-1", // 6
165 "abcdef", // 7
166 "ABCDEF", // 8
167 "12KK", // 9
168 "0123456789ABCDEFGH", // 10
169 "rJxKV9e9p6wiPw!!!!xrJ4X1n98LosPL1sgcJW", // 11
170 "rPSTrR5yEr11uMkfsz1kHCp9jK4aoa3Avv", // 12
171 "n9K2isxwTxcSHJKxMkJznDoWXAUs7NNy49H9Fknz1pC7oHAH3kH9", // 13
172 "USD", // 14
173 "USDollars", // 15
174 "5233D68B4D44388F98559DE42903767803EFA7C1F8D01413FC16EE6B01403D"
175 "6D", // 16
176 Json::arrayValue, // 17
177 Json::objectValue, // 18
178 injectObject, // 19
179 injectArray // 20
180 };
181
182 auto remove =
185 indices.begin(), indices.end());
187 values.reserve(allBadValues.size() - indexSet.size());
188 for (std::size_t i = 0; i < allBadValues.size(); ++i)
189 {
190 if (indexSet.find(i) == indexSet.end())
191 {
192 values.push_back(allBadValues[i]);
193 }
194 }
195 return values;
196 };
197
198 static auto const& badAccountValues = remove({12});
199 static auto const& badArrayValues = remove({17, 20});
200 static auto const& badBlobValues = remove({3, 7, 8, 16});
201 static auto const& badCurrencyValues = remove({14});
202 static auto const& badHashValues = remove({2, 3, 7, 8, 16});
203 static auto const& badFixedHashValues = remove({1, 2, 3, 4, 7, 8, 16});
204 static auto const& badIndexValues = remove({12, 16, 18, 19});
205 static auto const& badUInt32Values = remove({2, 3});
206 static auto const& badUInt64Values = remove({2, 3});
207 static auto const& badIssueValues = remove({});
208
209 switch (fieldType)
210 {
212 return badAccountValues;
215 return badArrayValues;
217 return badBlobValues;
219 return badCurrencyValues;
221 return badHashValues;
223 return badIndexValues;
225 return badFixedHashValues;
227 return badIssueValues;
229 return badUInt32Values;
231 return badUInt64Values;
232 default:
233 Throw<std::runtime_error>(
234 "unknown type " +
235 std::to_string(static_cast<uint8_t>(fieldType)));
236 }
237 }
238
241 {
242 static Json::Value const twoAccountArray = []() {
244 arr[0u] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
245 arr[1u] = "r4MrUGTdB57duTnRs6KbsRGQXgkseGb1b5";
246 return arr;
247 }();
248 static Json::Value const issueObject = []() {
250 arr[jss::currency] = "XRP";
251 return arr;
252 }();
253
254 auto const typeID = getFieldType(fieldName);
255 switch (typeID)
256 {
258 return "r4MrUGTdB57duTnRs6KbsRGQXgkseGb1b5";
260 return Json::arrayValue;
262 return "ABCDEF";
264 return "USD";
266 return "5233D68B4D44388F98559DE42903767803EFA7C1F8D01413FC16EE6"
267 "B01403D6D";
269 return issueObject;
271 return "5233D68B4D44388F98559DE42903767803EFA7C1F8D01413FC16EE6"
272 "B01403D6D";
274 return twoAccountArray;
276 return 1;
278 return 1;
279 default:
280 Throw<std::runtime_error>(
281 "unknown type " +
282 std::to_string(static_cast<uint8_t>(typeID)));
283 }
284 }
285
286 void
288 test::jtx::Env& env,
289 Json::Value correctRequest,
290 Json::StaticString const fieldName,
291 FieldType const typeID,
292 std::string const& expectedError,
293 bool required = true,
295 {
296 forAllApiVersions([&, this](unsigned apiVersion) {
297 if (required)
298 {
299 correctRequest.removeMember(fieldName);
300 Json::Value const jrr = env.rpc(
301 apiVersion,
302 "json",
303 "ledger_entry",
304 to_string(correctRequest))[jss::result];
305 if (apiVersion < 2u)
306 checkErrorValue(jrr, "unknownOption", "", location);
307 else
309 jrr,
310 "invalidParams",
311 "No ledger_entry params provided.",
312 location);
313 }
314 auto tryField = [&](Json::Value fieldValue) -> void {
315 correctRequest[fieldName] = fieldValue;
316 Json::Value const jrr = env.rpc(
317 apiVersion,
318 "json",
319 "ledger_entry",
320 to_string(correctRequest))[jss::result];
321 auto const expectedErrMsg =
322 RPC::expected_field_message(fieldName, getTypeName(typeID));
323 checkErrorValue(jrr, expectedError, expectedErrMsg, location);
324 };
325
326 auto const& badValues = getBadValues(typeID);
327 for (auto const& value : badValues)
328 {
329 tryField(value);
330 }
331 if (required)
332 {
333 tryField(Json::nullValue);
334 }
335 });
336 }
337
338 void
340 test::jtx::Env& env,
341 Json::Value correctRequest,
342 Json::StaticString parentFieldName,
343 Json::StaticString fieldName,
344 FieldType typeID,
345 std::string const& expectedError,
346 bool required = true,
348 {
349 forAllApiVersions([&, this](unsigned apiVersion) {
350 if (required)
351 {
352 correctRequest[parentFieldName].removeMember(fieldName);
353 Json::Value const jrr = env.rpc(
354 apiVersion,
355 "json",
356 "ledger_entry",
357 to_string(correctRequest))[jss::result];
359 jrr,
360 "malformedRequest",
362 location);
363
364 correctRequest[parentFieldName][fieldName] = Json::nullValue;
365 Json::Value const jrr2 = env.rpc(
366 apiVersion,
367 "json",
368 "ledger_entry",
369 to_string(correctRequest))[jss::result];
371 jrr2,
372 "malformedRequest",
374 location);
375 }
376 auto tryField = [&](Json::Value fieldValue) -> void {
377 correctRequest[parentFieldName][fieldName] = fieldValue;
378
379 Json::Value const jrr = env.rpc(
380 apiVersion,
381 "json",
382 "ledger_entry",
383 to_string(correctRequest))[jss::result];
385 jrr,
386 expectedError,
387 RPC::expected_field_message(fieldName, getTypeName(typeID)),
388 location);
389 };
390
391 auto const& badValues = getBadValues(typeID);
392 for (auto const& value : badValues)
393 {
394 tryField(value);
395 }
396 });
397 }
398
399 // No subfields
400 void
402 test::jtx::Env& env,
403 Json::StaticString const& parentField,
405 {
407 env,
408 Json::Value{},
409 parentField,
411 "malformedRequest",
412 true,
413 location);
414 }
415
422
423 void
425 test::jtx::Env& env,
426 Json::StaticString const& parentField,
427 std::vector<Subfield> const& subfields,
429 {
431 env,
432 Json::Value{},
433 parentField,
435 "malformedRequest",
436 true,
437 location);
438
439 Json::Value correctOutput;
440 correctOutput[parentField] = Json::objectValue;
441 for (auto const& subfield : subfields)
442 {
443 correctOutput[parentField][subfield.fieldName] =
444 getCorrectValue(subfield.fieldName);
445 }
446
447 for (auto const& subfield : subfields)
448 {
449 auto const fieldType = getFieldType(subfield.fieldName);
451 env,
452 correctOutput,
453 parentField,
454 subfield.fieldName,
455 fieldType,
456 subfield.malformedErrorMsg,
457 subfield.required,
458 location);
459 }
460 }
461
462 void
464 {
465 testcase("Invalid requests");
466 using namespace test::jtx;
467 Env env{*this};
468 Account const alice{"alice"};
469 env.fund(XRP(10000), alice);
470 env.close();
471 {
472 // Missing ledger_entry ledger_hash
473 Json::Value jvParams;
474 jvParams[jss::account_root] = alice.human();
475 jvParams[jss::ledger_hash] =
476 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
477 "AA";
478 auto const jrr = env.rpc(
479 "json", "ledger_entry", to_string(jvParams))[jss::result];
480 checkErrorValue(jrr, "lgrNotFound", "ledgerNotFound");
481 }
482 {
483 // Missing ledger_entry ledger_hash
484 Json::Value jvParams;
485 jvParams[jss::account_root] = alice.human();
486 auto const typeId = FieldType::HashField;
487
488 forAllApiVersions([&, this](unsigned apiVersion) {
489 auto tryField = [&](Json::Value fieldValue) -> void {
490 jvParams[jss::ledger_hash] = fieldValue;
491 Json::Value const jrr = env.rpc(
492 apiVersion,
493 "json",
494 "ledger_entry",
495 to_string(jvParams))[jss::result];
497 jrr,
498 "invalidParams",
499 "Invalid field 'ledger_hash', not hex string.");
500 };
501
502 auto const& badValues = getBadValues(typeId);
503 for (auto const& value : badValues)
504 {
505 tryField(value);
506 }
507 });
508 }
509
510 {
511 // ask for an zero index
512 Json::Value jvParams;
513 jvParams[jss::ledger_index] = "validated";
514 jvParams[jss::index] =
515 "00000000000000000000000000000000000000000000000000000000000000"
516 "00";
517 auto const jrr = env.rpc(
518 "json", "ledger_entry", to_string(jvParams))[jss::result];
519 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
520 }
521
522 forAllApiVersions([&, this](unsigned apiVersion) {
523 // "features" is not an option supported by ledger_entry.
524 {
526 jvParams[jss::features] =
527 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
528 "AAAAAAAAAA";
529 jvParams[jss::api_version] = apiVersion;
530 Json::Value const jrr = env.rpc(
531 "json", "ledger_entry", to_string(jvParams))[jss::result];
532
533 if (apiVersion < 2u)
534 checkErrorValue(jrr, "unknownOption", "");
535 else
537 jrr,
538 "invalidParams",
539 "No ledger_entry params provided.");
540 }
541 });
542 }
543
544 void
546 {
547 testcase("AccountRoot");
548 using namespace test::jtx;
549
550 auto cfg = envconfig();
551 cfg->FEES.reference_fee = 10;
552 Env env{*this, std::move(cfg)};
553
554 Account const alice{"alice"};
555 env.fund(XRP(10000), alice);
556 env.close();
557
558 std::string const ledgerHash{to_string(env.closed()->header().hash)};
559 {
560 // Exercise ledger_closed along the way.
561 Json::Value const jrr = env.rpc("ledger_closed")[jss::result];
562 BEAST_EXPECT(jrr[jss::ledger_hash] == ledgerHash);
563 BEAST_EXPECT(jrr[jss::ledger_index] == 3);
564 }
565
566 std::string accountRootIndex;
567 {
568 // Request alice's account root.
569 Json::Value jvParams;
570 jvParams[jss::account_root] = alice.human();
571 jvParams[jss::ledger_hash] = ledgerHash;
572 Json::Value const jrr = env.rpc(
573 "json", "ledger_entry", to_string(jvParams))[jss::result];
574 BEAST_EXPECT(jrr.isMember(jss::node));
575 BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
576 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
577 accountRootIndex = jrr[jss::index].asString();
578 }
579 {
580 constexpr char alicesAcctRootBinary[]{
581 "1100612200800000240000000425000000032D00000000559CE54C3B934E4"
582 "73A995B477E92EC229F99CED5B62BF4D2ACE4DC42719103AE2F6240000002"
583 "540BE4008114AE123A8556F3CF91154711376AFB0F894F832B3D"};
584
585 // Request alice's account root, but with binary == true;
586 Json::Value jvParams;
587 jvParams[jss::account_root] = alice.human();
588 jvParams[jss::binary] = 1;
589 jvParams[jss::ledger_hash] = ledgerHash;
590 Json::Value const jrr = env.rpc(
591 "json", "ledger_entry", to_string(jvParams))[jss::result];
592 BEAST_EXPECT(jrr.isMember(jss::node_binary));
593 BEAST_EXPECT(jrr[jss::node_binary] == alicesAcctRootBinary);
594 }
595 {
596 // Request alice's account root using the index.
597 Json::Value jvParams;
598 jvParams[jss::index] = accountRootIndex;
599 Json::Value const jrr = env.rpc(
600 "json", "ledger_entry", to_string(jvParams))[jss::result];
601 BEAST_EXPECT(!jrr.isMember(jss::node_binary));
602 BEAST_EXPECT(jrr.isMember(jss::node));
603 BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
604 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
605 }
606 {
607 // Request alice's account root by index, but with binary == false.
608 Json::Value jvParams;
609 jvParams[jss::index] = accountRootIndex;
610 jvParams[jss::binary] = 0;
611 Json::Value const jrr = env.rpc(
612 "json", "ledger_entry", to_string(jvParams))[jss::result];
613 BEAST_EXPECT(jrr.isMember(jss::node));
614 BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
615 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
616 }
617 {
618 // Check alias
619 Json::Value jvParams;
620 jvParams[jss::account] = alice.human();
621 jvParams[jss::ledger_hash] = ledgerHash;
622 Json::Value const jrr = env.rpc(
623 "json", "ledger_entry", to_string(jvParams))[jss::result];
624 BEAST_EXPECT(jrr.isMember(jss::node));
625 BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
626 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
627 accountRootIndex = jrr[jss::index].asString();
628 }
629 {
630 // Check malformed cases
631 Json::Value jvParams;
633 env,
634 jvParams,
635 jss::account_root,
637 "malformedAddress");
638 }
639 {
640 // Request an account that is not in the ledger.
641 Json::Value jvParams;
642 jvParams[jss::account_root] = Account("bob").human();
643 jvParams[jss::ledger_hash] = ledgerHash;
644 Json::Value const jrr = env.rpc(
645 "json", "ledger_entry", to_string(jvParams))[jss::result];
646 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
647 }
648 }
649
650 void
652 {
653 testcase("Amendments");
654 using namespace test::jtx;
655 Env env{*this};
656
657 // positive test
658 {
659 Keylet const keylet = keylet::amendments();
660
661 // easier to hack an object into the ledger than generate it
662 // legitimately
663 {
664 auto const amendments = [&](OpenView& view,
665 beast::Journal) -> bool {
666 auto const sle = std::make_shared<SLE>(keylet);
667
668 // Create Amendments vector (enabled amendments)
669 std::vector<uint256> enabledAmendments;
670 enabledAmendments.push_back(
671 uint256::fromVoid("42426C4D4F1009EE67080A9B7965B44656D7"
672 "714D104A72F9B4369F97ABF044EE"));
673 enabledAmendments.push_back(
674 uint256::fromVoid("4C97EBA926031A7CF7D7B36FDE3ED66DDA54"
675 "21192D63DE53FFB46E43B9DC8373"));
676 enabledAmendments.push_back(
677 uint256::fromVoid("03BDC0099C4E14163ADA272C1B6F6FABB448"
678 "CC3E51F522F978041E4B57D9158C"));
679 enabledAmendments.push_back(
680 uint256::fromVoid("35291ADD2D79EB6991343BDA0912269C817D"
681 "0F094B02226C1C14AD2858962ED4"));
682 sle->setFieldV256(
683 sfAmendments, STVector256(enabledAmendments));
684
685 // Create Majorities array
686 STArray majorities;
687
688 auto majority1 = STObject::makeInnerObject(sfMajority);
689 majority1.setFieldH256(
690 sfAmendment,
691 uint256::fromVoid("7BB62DC13EC72B775091E9C71BF8CF97E122"
692 "647693B50C5E87A80DFD6FCFAC50"));
693 majority1.setFieldU32(sfCloseTime, 779561310);
694 majorities.push_back(std::move(majority1));
695
696 auto majority2 = STObject::makeInnerObject(sfMajority);
697 majority2.setFieldH256(
698 sfAmendment,
699 uint256::fromVoid("755C971C29971C9F20C6F080F2ED96F87884"
700 "E40AD19554A5EBECDCEC8A1F77FE"));
701 majority2.setFieldU32(sfCloseTime, 779561310);
702 majorities.push_back(std::move(majority2));
703
704 sle->setFieldArray(sfMajorities, majorities);
705
706 view.rawInsert(sle);
707 return true;
708 };
709 env.app().openLedger().modify(amendments);
710 }
711
712 Json::Value jvParams;
713 jvParams[jss::amendments] = to_string(keylet.key);
714 Json::Value const jrr = env.rpc(
715 "json", "ledger_entry", to_string(jvParams))[jss::result];
716 BEAST_EXPECT(
717 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Amendments);
718 }
719
720 // negative tests
722 env,
723 Json::Value{},
724 jss::amendments,
726 "malformedRequest");
727 }
728
729 void
731 {
732 testcase("AMM");
733 using namespace test::jtx;
734 Env env{*this};
735
736 // positive test
737 Account const alice{"alice"};
738 env.fund(XRP(10000), alice);
739 env.close();
740 AMM amm(env, alice, XRP(10), alice["USD"](1000));
741 env.close();
742
743 {
744 Json::Value jvParams;
745 jvParams[jss::amm] = to_string(amm.ammID());
746 auto const result =
747 env.rpc("json", "ledger_entry", to_string(jvParams));
748 BEAST_EXPECT(
749 result.isObject() && result.isMember(jss::result) &&
750 !result[jss::result].isMember(jss::error) &&
751 result[jss::result].isMember(jss::node) &&
752 result[jss::result][jss::node].isMember(
753 sfLedgerEntryType.jsonName) &&
754 result[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
755 jss::AMM);
756 }
757
758 {
759 Json::Value jvParams;
761 {
763 obj[jss::currency] = "XRP";
764 ammParams[jss::asset] = obj;
765 }
766 {
768 obj[jss::currency] = "USD";
769 obj[jss::issuer] = alice.human();
770 ammParams[jss::asset2] = obj;
771 }
772 jvParams[jss::amm] = ammParams;
773 auto const result =
774 env.rpc("json", "ledger_entry", to_string(jvParams));
775 BEAST_EXPECT(
776 result.isObject() && result.isMember(jss::result) &&
777 !result[jss::result].isMember(jss::error) &&
778 result[jss::result].isMember(jss::node) &&
779 result[jss::result][jss::node].isMember(
780 sfLedgerEntryType.jsonName) &&
781 result[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
782 jss::AMM);
783 }
784
785 // negative tests
787 env,
788 jss::amm,
789 {
790 {jss::asset, "malformedRequest"},
791 {jss::asset2, "malformedRequest"},
792 });
793 }
794
795 void
797 {
798 testcase("Check");
799 using namespace test::jtx;
800 Env env{*this};
801 Account const alice{"alice"};
802 env.fund(XRP(10000), alice);
803 env.close();
804
805 auto const checkId = keylet::check(env.master, env.seq(env.master));
806
807 env(check::create(env.master, alice, XRP(100)));
808 env.close();
809
810 std::string const ledgerHash{to_string(env.closed()->header().hash)};
811 {
812 // Request a check.
813 Json::Value jvParams;
814 jvParams[jss::check] = to_string(checkId.key);
815 jvParams[jss::ledger_hash] = ledgerHash;
816 Json::Value const jrr = env.rpc(
817 "json", "ledger_entry", to_string(jvParams))[jss::result];
818 BEAST_EXPECT(
819 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Check);
820 BEAST_EXPECT(jrr[jss::node][sfSendMax.jsonName] == "100000000");
821 }
822 {
823 // Request an index that is not a check. We'll use alice's
824 // account root index.
825 std::string accountRootIndex;
826 {
827 Json::Value jvParams;
828 jvParams[jss::account_root] = alice.human();
829 Json::Value const jrr = env.rpc(
830 "json", "ledger_entry", to_string(jvParams))[jss::result];
831 accountRootIndex = jrr[jss::index].asString();
832 }
833 Json::Value jvParams;
834 jvParams[jss::check] = accountRootIndex;
835 jvParams[jss::ledger_hash] = ledgerHash;
836 Json::Value const jrr = env.rpc(
837 "json", "ledger_entry", to_string(jvParams))[jss::result];
839 jrr, "unexpectedLedgerType", "Unexpected ledger type.");
840 }
841 {
842 // Check malformed cases
843 runLedgerEntryTest(env, jss::check);
844 }
845 }
846
847 void
849 {
850 testcase("Credentials");
851
852 using namespace test::jtx;
853
854 Env env(*this);
855 Account const issuer{"issuer"};
856 Account const alice{"alice"};
857 Account const bob{"bob"};
858 char const credType[] = "abcde";
859
860 env.fund(XRP(5000), issuer, alice, bob);
861 env.close();
862
863 // Setup credentials with DepositAuth object for Alice and Bob
864 env(credentials::create(alice, issuer, credType));
865 env.close();
866
867 {
868 // Succeed
869 auto jv = credentials::ledgerEntry(env, alice, issuer, credType);
870 BEAST_EXPECT(
871 jv.isObject() && jv.isMember(jss::result) &&
872 !jv[jss::result].isMember(jss::error) &&
873 jv[jss::result].isMember(jss::node) &&
874 jv[jss::result][jss::node].isMember(
875 sfLedgerEntryType.jsonName) &&
876 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
877 jss::Credential);
878
879 std::string const credIdx = jv[jss::result][jss::index].asString();
880
881 jv = credentials::ledgerEntry(env, credIdx);
882 BEAST_EXPECT(
883 jv.isObject() && jv.isMember(jss::result) &&
884 !jv[jss::result].isMember(jss::error) &&
885 jv[jss::result].isMember(jss::node) &&
886 jv[jss::result][jss::node].isMember(
887 sfLedgerEntryType.jsonName) &&
888 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
889 jss::Credential);
890 }
891
892 {
893 // Fail, credential doesn't exist
894 auto const jv = credentials::ledgerEntry(
895 env,
896 "48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
897 "E4");
899 jv[jss::result], "entryNotFound", "Entry not found.");
900 }
901
902 {
903 // Check all malformed cases
905 env,
906 jss::credential,
907 {
908 {jss::subject, "malformedRequest"},
909 {jss::issuer, "malformedRequest"},
910 {jss::credential_type, "malformedRequest"},
911 });
912 }
913 }
914
915 void
917 {
918 testcase("Delegate");
919
920 using namespace test::jtx;
921
922 Env env{*this};
923 Account const alice{"alice"};
924 Account const bob{"bob"};
925 env.fund(XRP(10000), alice, bob);
926 env.close();
927 env(delegate::set(alice, bob, {"Payment", "CheckCreate"}));
928 env.close();
929 std::string const ledgerHash{to_string(env.closed()->header().hash)};
930 std::string delegateIndex;
931 {
932 // Request by account and authorize
933 Json::Value jvParams;
934 jvParams[jss::delegate][jss::account] = alice.human();
935 jvParams[jss::delegate][jss::authorize] = bob.human();
936 jvParams[jss::ledger_hash] = ledgerHash;
937 Json::Value const jrr = env.rpc(
938 "json", "ledger_entry", to_string(jvParams))[jss::result];
939 BEAST_EXPECT(
940 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Delegate);
941 BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
942 BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == bob.human());
943 delegateIndex = jrr[jss::node][jss::index].asString();
944 }
945 {
946 // Request by index.
947 Json::Value jvParams;
948 jvParams[jss::delegate] = delegateIndex;
949 jvParams[jss::ledger_hash] = ledgerHash;
950 Json::Value const jrr = env.rpc(
951 "json", "ledger_entry", to_string(jvParams))[jss::result];
952 BEAST_EXPECT(
953 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Delegate);
954 BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
955 BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == bob.human());
956 }
957
958 {
959 // Check all malformed cases
961 env,
962 jss::delegate,
963 {
964 {jss::account, "malformedAddress"},
965 {jss::authorize, "malformedAddress"},
966 });
967 }
968 }
969
970 void
972 {
973 testcase("Deposit Preauth");
974
975 using namespace test::jtx;
976
977 Env env{*this};
978 Account const alice{"alice"};
979 Account const becky{"becky"};
980
981 env.fund(XRP(10000), alice, becky);
982 env.close();
983
984 env(deposit::auth(alice, becky));
985 env.close();
986
987 std::string const ledgerHash{to_string(env.closed()->header().hash)};
988 std::string depositPreauthIndex;
989 {
990 // Request a depositPreauth by owner and authorized.
991 Json::Value jvParams;
992 jvParams[jss::deposit_preauth][jss::owner] = alice.human();
993 jvParams[jss::deposit_preauth][jss::authorized] = becky.human();
994 jvParams[jss::ledger_hash] = ledgerHash;
995 Json::Value const jrr = env.rpc(
996 "json", "ledger_entry", to_string(jvParams))[jss::result];
997
998 BEAST_EXPECT(
999 jrr[jss::node][sfLedgerEntryType.jsonName] ==
1000 jss::DepositPreauth);
1001 BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
1002 BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == becky.human());
1003 depositPreauthIndex = jrr[jss::node][jss::index].asString();
1004 }
1005 {
1006 // Request a depositPreauth by index.
1007 Json::Value jvParams;
1008 jvParams[jss::deposit_preauth] = depositPreauthIndex;
1009 jvParams[jss::ledger_hash] = ledgerHash;
1010 Json::Value const jrr = env.rpc(
1011 "json", "ledger_entry", to_string(jvParams))[jss::result];
1012
1013 BEAST_EXPECT(
1014 jrr[jss::node][sfLedgerEntryType.jsonName] ==
1015 jss::DepositPreauth);
1016 BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
1017 BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == becky.human());
1018 }
1019 {
1020 // test all missing/malformed field cases
1022 env,
1023 jss::deposit_preauth,
1024 {
1025 {jss::owner, "malformedOwner"},
1026 {jss::authorized, "malformedAuthorized", false},
1027 });
1028 }
1029 }
1030
1031 void
1033 {
1034 testcase("Deposit Preauth with credentials");
1035
1036 using namespace test::jtx;
1037
1038 Env env(*this);
1039 Account const issuer{"issuer"};
1040 Account const alice{"alice"};
1041 Account const bob{"bob"};
1042 char const credType[] = "abcde";
1043
1044 env.fund(XRP(5000), issuer, alice, bob);
1045 env.close();
1046
1047 {
1048 // Setup Bob with DepositAuth
1049 env(fset(bob, asfDepositAuth));
1050 env.close();
1051 env(deposit::authCredentials(bob, {{issuer, credType}}));
1052 env.close();
1053 }
1054
1055 {
1056 // Succeed
1057 Json::Value jvParams;
1058 jvParams[jss::ledger_index] = jss::validated;
1059 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1060
1061 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
1063 auto& arr(
1064 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
1065
1066 Json::Value jo;
1067 jo[jss::issuer] = issuer.human();
1068 jo[jss::credential_type] = strHex(std::string_view(credType));
1069 arr.append(std::move(jo));
1070 auto const jrr =
1071 env.rpc("json", "ledger_entry", to_string(jvParams));
1072
1073 BEAST_EXPECT(
1074 jrr.isObject() && jrr.isMember(jss::result) &&
1075 !jrr[jss::result].isMember(jss::error) &&
1076 jrr[jss::result].isMember(jss::node) &&
1077 jrr[jss::result][jss::node].isMember(
1078 sfLedgerEntryType.jsonName) &&
1079 jrr[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
1080 jss::DepositPreauth);
1081 }
1082
1083 {
1084 // Failed, invalid account
1085 Json::Value jvParams;
1086 jvParams[jss::ledger_index] = jss::validated;
1087 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1088
1089 auto tryField = [&](Json::Value fieldValue) -> void {
1091 Json::Value jo;
1092 jo[jss::issuer] = fieldValue;
1093 jo[jss::credential_type] = strHex(std::string_view(credType));
1094 arr.append(jo);
1095 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
1096 arr;
1097
1098 Json::Value const jrr = env.rpc(
1099 "json", "ledger_entry", to_string(jvParams))[jss::result];
1100 auto const expectedErrMsg = fieldValue.isNull()
1101 ? RPC::missing_field_message(jss::issuer.c_str())
1102 : RPC::expected_field_message(jss::issuer, "AccountID");
1104 jrr, "malformedAuthorizedCredentials", expectedErrMsg);
1105 };
1106
1107 auto const& badValues = getBadValues(FieldType::AccountField);
1108 for (auto const& value : badValues)
1109 {
1110 tryField(value);
1111 }
1112 tryField(Json::nullValue);
1113 }
1114
1115 {
1116 // Failed, duplicates in credentials
1117 Json::Value jvParams;
1118 jvParams[jss::ledger_index] = jss::validated;
1119 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1120
1121 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
1123 auto& arr(
1124 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
1125
1126 Json::Value jo;
1127 jo[jss::issuer] = issuer.human();
1128 jo[jss::credential_type] = strHex(std::string_view(credType));
1129 arr.append(jo);
1130 arr.append(std::move(jo));
1131 auto const jrr =
1132 env.rpc("json", "ledger_entry", to_string(jvParams));
1134 jrr[jss::result],
1135 "malformedAuthorizedCredentials",
1137 jss::authorized_credentials, "array"));
1138 }
1139
1140 {
1141 // Failed, invalid credential_type
1142 Json::Value jvParams;
1143 jvParams[jss::ledger_index] = jss::validated;
1144 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1145
1146 auto tryField = [&](Json::Value fieldValue) -> void {
1148 Json::Value jo;
1149 jo[jss::issuer] = issuer.human();
1150 jo[jss::credential_type] = fieldValue;
1151 arr.append(jo);
1152 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
1153 arr;
1154
1155 Json::Value const jrr = env.rpc(
1156 "json", "ledger_entry", to_string(jvParams))[jss::result];
1157 auto const expectedErrMsg = fieldValue.isNull()
1158 ? RPC::missing_field_message(jss::credential_type.c_str())
1160 jss::credential_type, "hex string");
1162 jrr, "malformedAuthorizedCredentials", expectedErrMsg);
1163 };
1164
1165 auto const& badValues = getBadValues(FieldType::BlobField);
1166 for (auto const& value : badValues)
1167 {
1168 tryField(value);
1169 }
1170 tryField(Json::nullValue);
1171 }
1172
1173 {
1174 // Failed, authorized and authorized_credentials both present
1175 Json::Value jvParams;
1176 jvParams[jss::ledger_index] = jss::validated;
1177 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1178 jvParams[jss::deposit_preauth][jss::authorized] = alice.human();
1179
1180 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
1182 auto& arr(
1183 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
1184
1185 Json::Value jo;
1186 jo[jss::issuer] = issuer.human();
1187 jo[jss::credential_type] = strHex(std::string_view(credType));
1188 arr.append(std::move(jo));
1189
1190 auto const jrr =
1191 env.rpc("json", "ledger_entry", to_string(jvParams));
1193 jrr[jss::result],
1194 "malformedRequest",
1195 "Must have exactly one of `authorized` and "
1196 "`authorized_credentials`.");
1197 }
1198
1199 {
1200 // Failed, authorized_credentials is not an array
1201 Json::Value jvParams;
1202 jvParams[jss::ledger_index] = jss::validated;
1203 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1205 env,
1206 jvParams,
1207 jss::deposit_preauth,
1208 jss::authorized_credentials,
1210 "malformedAuthorizedCredentials",
1211 false);
1212 }
1213
1214 {
1215 // Failed, authorized_credentials contains string data
1216 Json::Value jvParams;
1217 jvParams[jss::ledger_index] = jss::validated;
1218 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1219 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
1221 auto& arr(
1222 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
1223 arr.append("foobar");
1224
1225 auto const jrr =
1226 env.rpc("json", "ledger_entry", to_string(jvParams));
1228 jrr[jss::result],
1229 "malformedAuthorizedCredentials",
1230 "Invalid field 'authorized_credentials', not array.");
1231 }
1232
1233 {
1234 // Failed, authorized_credentials contains arrays
1235 Json::Value jvParams;
1236 jvParams[jss::ledger_index] = jss::validated;
1237 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1238 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
1240 auto& arr(
1241 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
1242 Json::Value payload = Json::arrayValue;
1243 payload.append(42);
1244 arr.append(std::move(payload));
1245
1246 auto const jrr =
1247 env.rpc("json", "ledger_entry", to_string(jvParams));
1249 jrr[jss::result],
1250 "malformedAuthorizedCredentials",
1251 "Invalid field 'authorized_credentials', not array.");
1252 }
1253
1254 {
1255 // Failed, authorized_credentials is empty array
1256 Json::Value jvParams;
1257 jvParams[jss::ledger_index] = jss::validated;
1258 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1259 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
1261
1262 auto const jrr =
1263 env.rpc("json", "ledger_entry", to_string(jvParams));
1265 jrr[jss::result],
1266 "malformedAuthorizedCredentials",
1267 "Invalid field 'authorized_credentials', array empty.");
1268 }
1269
1270 {
1271 // Failed, authorized_credentials is too long
1272 static std::array<std::string_view, 9> const credTypes = {
1273 "cred1",
1274 "cred2",
1275 "cred3",
1276 "cred4",
1277 "cred5",
1278 "cred6",
1279 "cred7",
1280 "cred8",
1281 "cred9"};
1282 static_assert(
1283 sizeof(credTypes) / sizeof(credTypes[0]) >
1285
1286 Json::Value jvParams;
1287 jvParams[jss::ledger_index] = jss::validated;
1288 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1289 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
1291
1292 auto& arr(
1293 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
1294
1295 for (auto cred : credTypes)
1296 {
1297 Json::Value jo;
1298 jo[jss::issuer] = issuer.human();
1299 jo[jss::credential_type] = strHex(std::string_view(cred));
1300 arr.append(std::move(jo));
1301 }
1302
1303 auto const jrr =
1304 env.rpc("json", "ledger_entry", to_string(jvParams));
1306 jrr[jss::result],
1307 "malformedAuthorizedCredentials",
1308 "Invalid field 'authorized_credentials', array too long.");
1309 }
1310 }
1311
1312 void
1314 {
1315 testcase("Directory");
1316 using namespace test::jtx;
1317 Env env{*this};
1318 Account const alice{"alice"};
1319 Account const gw{"gateway"};
1320 auto const USD = gw["USD"];
1321 env.fund(XRP(10000), alice, gw);
1322 env.close();
1323
1324 env.trust(USD(1000), alice);
1325 env.close();
1326
1327 // Run up the number of directory entries so alice has two
1328 // directory nodes.
1329 for (int d = 1'000'032; d >= 1'000'000; --d)
1330 {
1331 env(offer(alice, USD(1), drops(d)));
1332 }
1333 env.close();
1334
1335 std::string const ledgerHash{to_string(env.closed()->header().hash)};
1336 {
1337 // Exercise ledger_closed along the way.
1338 Json::Value const jrr = env.rpc("ledger_closed")[jss::result];
1339 BEAST_EXPECT(jrr[jss::ledger_hash] == ledgerHash);
1340 BEAST_EXPECT(jrr[jss::ledger_index] == 5);
1341 }
1342
1343 std::string const dirRootIndex =
1344 "A33EC6BB85FB5674074C4A3A43373BB17645308F3EAE1933E3E35252162B217D";
1345 {
1346 // Locate directory by index.
1347 Json::Value jvParams;
1348 jvParams[jss::directory] = dirRootIndex;
1349 jvParams[jss::ledger_hash] = ledgerHash;
1350 Json::Value const jrr = env.rpc(
1351 "json", "ledger_entry", to_string(jvParams))[jss::result];
1352 BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 32);
1353 }
1354 {
1355 // Locate directory by directory root.
1356 Json::Value jvParams;
1357 jvParams[jss::directory] = Json::objectValue;
1358 jvParams[jss::directory][jss::dir_root] = dirRootIndex;
1359 Json::Value const jrr = env.rpc(
1360 "json", "ledger_entry", to_string(jvParams))[jss::result];
1361 BEAST_EXPECT(jrr[jss::index] == dirRootIndex);
1362 }
1363 {
1364 // Locate directory by owner.
1365 Json::Value jvParams;
1366 jvParams[jss::directory] = Json::objectValue;
1367 jvParams[jss::directory][jss::owner] = alice.human();
1368 jvParams[jss::ledger_hash] = ledgerHash;
1369 Json::Value const jrr = env.rpc(
1370 "json", "ledger_entry", to_string(jvParams))[jss::result];
1371 BEAST_EXPECT(jrr[jss::index] == dirRootIndex);
1372 }
1373 {
1374 // Locate directory by directory root and sub_index.
1375 Json::Value jvParams;
1376 jvParams[jss::directory] = Json::objectValue;
1377 jvParams[jss::directory][jss::dir_root] = dirRootIndex;
1378 jvParams[jss::directory][jss::sub_index] = 1;
1379 Json::Value const jrr = env.rpc(
1380 "json", "ledger_entry", to_string(jvParams))[jss::result];
1381 BEAST_EXPECT(jrr[jss::index] != dirRootIndex);
1382 BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 2);
1383 }
1384 {
1385 // Locate directory by owner and sub_index.
1386 Json::Value jvParams;
1387 jvParams[jss::directory] = Json::objectValue;
1388 jvParams[jss::directory][jss::owner] = alice.human();
1389 jvParams[jss::directory][jss::sub_index] = 1;
1390 jvParams[jss::ledger_hash] = ledgerHash;
1391 Json::Value const jrr = env.rpc(
1392 "json", "ledger_entry", to_string(jvParams))[jss::result];
1393 BEAST_EXPECT(jrr[jss::index] != dirRootIndex);
1394 BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 2);
1395 }
1396 {
1397 // Bad directory argument.
1398 Json::Value jvParams;
1399 jvParams[jss::ledger_hash] = ledgerHash;
1401 env,
1402 jvParams,
1403 jss::directory,
1405 "malformedRequest");
1406 }
1407 {
1408 // Non-integer sub_index.
1409 Json::Value jvParams;
1410 jvParams[jss::directory] = Json::objectValue;
1411 jvParams[jss::directory][jss::dir_root] = dirRootIndex;
1412 jvParams[jss::ledger_hash] = ledgerHash;
1414 env,
1415 jvParams,
1416 jss::directory,
1417 jss::sub_index,
1419 "malformedRequest",
1420 false);
1421 }
1422 {
1423 // Malformed owner entry.
1424 Json::Value jvParams;
1425 jvParams[jss::directory] = Json::objectValue;
1426
1427 jvParams[jss::ledger_hash] = ledgerHash;
1429 env,
1430 jvParams,
1431 jss::directory,
1432 jss::owner,
1434 "malformedAddress",
1435 false);
1436 }
1437 {
1438 // Malformed directory object. Specifies both dir_root and owner.
1439 Json::Value jvParams;
1440 jvParams[jss::directory] = Json::objectValue;
1441 jvParams[jss::directory][jss::owner] = alice.human();
1442 jvParams[jss::directory][jss::dir_root] = dirRootIndex;
1443 jvParams[jss::ledger_hash] = ledgerHash;
1444 Json::Value const jrr = env.rpc(
1445 "json", "ledger_entry", to_string(jvParams))[jss::result];
1447 jrr,
1448 "malformedRequest",
1449 "Must have exactly one of `owner` and `dir_root` fields.");
1450 }
1451 {
1452 // Incomplete directory object. Missing both dir_root and owner.
1453 Json::Value jvParams;
1454 jvParams[jss::directory] = Json::objectValue;
1455 jvParams[jss::directory][jss::sub_index] = 1;
1456 jvParams[jss::ledger_hash] = ledgerHash;
1457 Json::Value const jrr = env.rpc(
1458 "json", "ledger_entry", to_string(jvParams))[jss::result];
1460 jrr,
1461 "malformedRequest",
1462 "Must have exactly one of `owner` and `dir_root` fields.");
1463 }
1464 }
1465
1466 void
1468 {
1469 testcase("Escrow");
1470 using namespace test::jtx;
1471 Env env{*this};
1472 Account const alice{"alice"};
1473 env.fund(XRP(10000), alice);
1474 env.close();
1475
1476 // Lambda to create an escrow.
1477 auto escrowCreate = [](test::jtx::Account const& account,
1478 test::jtx::Account const& to,
1479 STAmount const& amount,
1480 NetClock::time_point const& cancelAfter) {
1481 Json::Value jv;
1482 jv[jss::TransactionType] = jss::EscrowCreate;
1483 jv[jss::Account] = account.human();
1484 jv[jss::Destination] = to.human();
1485 jv[jss::Amount] = amount.getJson(JsonOptions::none);
1486 jv[sfFinishAfter.jsonName] =
1487 cancelAfter.time_since_epoch().count() + 2;
1488 return jv;
1489 };
1490
1491 using namespace std::chrono_literals;
1492 env(escrowCreate(alice, alice, XRP(333), env.now() + 2s));
1493 env.close();
1494
1495 std::string const ledgerHash{to_string(env.closed()->header().hash)};
1496 std::string escrowIndex;
1497 {
1498 // Request the escrow using owner and sequence.
1499 Json::Value jvParams;
1500 jvParams[jss::escrow] = Json::objectValue;
1501 jvParams[jss::escrow][jss::owner] = alice.human();
1502 jvParams[jss::escrow][jss::seq] = env.seq(alice) - 1;
1503 Json::Value const jrr = env.rpc(
1504 "json", "ledger_entry", to_string(jvParams))[jss::result];
1505 BEAST_EXPECT(
1506 jrr[jss::node][jss::Amount] == XRP(333).value().getText());
1507 escrowIndex = jrr[jss::index].asString();
1508 }
1509 {
1510 // Request the escrow by index.
1511 Json::Value jvParams;
1512 jvParams[jss::escrow] = escrowIndex;
1513 jvParams[jss::ledger_hash] = ledgerHash;
1514 Json::Value const jrr = env.rpc(
1515 "json", "ledger_entry", to_string(jvParams))[jss::result];
1516 BEAST_EXPECT(
1517 jrr[jss::node][jss::Amount] == XRP(333).value().getText());
1518 }
1519 {
1520 // Malformed escrow fields
1522 env,
1523 jss::escrow,
1524 {{jss::owner, "malformedOwner"}, {jss::seq, "malformedSeq"}});
1525 }
1526 }
1527
1528 void
1530 {
1531 testcase("Fee Settings");
1532 using namespace test::jtx;
1533 Env env{*this};
1534
1535 // positive test
1536 {
1537 Keylet const keylet = keylet::fees();
1538 Json::Value jvParams;
1539 jvParams[jss::fee] = to_string(keylet.key);
1540 Json::Value const jrr = env.rpc(
1541 "json", "ledger_entry", to_string(jvParams))[jss::result];
1542 BEAST_EXPECT(
1543 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::FeeSettings);
1544 }
1545
1546 // negative tests
1548 env,
1549 Json::Value{},
1550 jss::fee,
1552 "malformedRequest");
1553 }
1554
1555 void
1557 {
1558 testcase("Ledger Hashes");
1559 using namespace test::jtx;
1560 Env env{*this};
1561
1562 // positive test
1563 {
1564 Keylet const keylet = keylet::skip();
1565 Json::Value jvParams;
1566 jvParams[jss::hashes] = to_string(keylet.key);
1567 Json::Value const jrr = env.rpc(
1568 "json", "ledger_entry", to_string(jvParams))[jss::result];
1569 BEAST_EXPECT(
1570 jrr[jss::node][sfLedgerEntryType.jsonName] ==
1571 jss::LedgerHashes);
1572 }
1573
1574 // negative tests
1576 env,
1577 Json::Value{},
1578 jss::hashes,
1580 "malformedRequest");
1581 }
1582
1583 void
1585 {
1586 testcase("NFT Offer");
1587 using namespace test::jtx;
1588 Env env{*this};
1589
1590 // positive test
1591 Account const issuer{"issuer"};
1592 Account const buyer{"buyer"};
1593 env.fund(XRP(1000), issuer, buyer);
1594
1595 uint256 const nftokenID0 =
1596 token::getNextID(env, issuer, 0, tfTransferable);
1597 env(token::mint(issuer, 0), txflags(tfTransferable));
1598 env.close();
1599 uint256 const offerID = keylet::nftoffer(issuer, env.seq(issuer)).key;
1600 env(token::createOffer(issuer, nftokenID0, drops(1)),
1601 token::destination(buyer),
1603
1604 {
1605 Json::Value jvParams;
1606 jvParams[jss::nft_offer] = to_string(offerID);
1607 Json::Value const jrr = env.rpc(
1608 "json", "ledger_entry", to_string(jvParams))[jss::result];
1609 BEAST_EXPECT(
1610 jrr[jss::node][sfLedgerEntryType.jsonName] ==
1611 jss::NFTokenOffer);
1612 BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == issuer.human());
1613 BEAST_EXPECT(
1614 jrr[jss::node][sfNFTokenID.jsonName] == to_string(nftokenID0));
1615 BEAST_EXPECT(jrr[jss::node][sfAmount.jsonName] == "1");
1616 }
1617
1618 // negative tests
1619 runLedgerEntryTest(env, jss::nft_offer);
1620 }
1621
1622 void
1624 {
1625 testcase("NFT Page");
1626 using namespace test::jtx;
1627 Env env{*this};
1628
1629 // positive test
1630 Account const issuer{"issuer"};
1631 env.fund(XRP(1000), issuer);
1632
1633 env(token::mint(issuer, 0), txflags(tfTransferable));
1634 env.close();
1635
1636 auto const nftpage = keylet::nftpage_max(issuer);
1637 BEAST_EXPECT(env.le(nftpage) != nullptr);
1638
1639 {
1640 Json::Value jvParams;
1641 jvParams[jss::nft_page] = to_string(nftpage.key);
1642 Json::Value const jrr = env.rpc(
1643 "json", "ledger_entry", to_string(jvParams))[jss::result];
1644 BEAST_EXPECT(
1645 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::NFTokenPage);
1646 }
1647
1648 // negative tests
1649 runLedgerEntryTest(env, jss::nft_page);
1650 }
1651
1652 void
1654 {
1655 testcase("Negative UNL");
1656 using namespace test::jtx;
1657 Env env{*this};
1658
1659 // positive test
1660 {
1661 Keylet const keylet = keylet::negativeUNL();
1662
1663 // easier to hack an object into the ledger than generate it
1664 // legitimately
1665 {
1666 auto const nUNL = [&](OpenView& view, beast::Journal) -> bool {
1667 auto const sle = std::make_shared<SLE>(keylet);
1668
1669 // Create DisabledValidators array
1670 STArray disabledValidators;
1671 auto disabledValidator =
1672 STObject::makeInnerObject(sfDisabledValidator);
1673 auto pubKeyBlob = strUnHex(
1674 "ED58F6770DB5DD77E59D28CB650EC3816E2FC95021BB56E720C9A1"
1675 "2DA79C58A3AB");
1676 disabledValidator.setFieldVL(sfPublicKey, *pubKeyBlob);
1677 disabledValidator.setFieldU32(
1678 sfFirstLedgerSequence, 91371264);
1679 disabledValidators.push_back(std::move(disabledValidator));
1680
1681 sle->setFieldArray(
1682 sfDisabledValidators, disabledValidators);
1683 sle->setFieldH256(
1684 sfPreviousTxnID,
1685 uint256::fromVoid("8D47FFE664BE6C335108DF689537625855A6"
1686 "A95160CC6D351341B9"
1687 "2624D9C5E3"));
1688 sle->setFieldU32(sfPreviousTxnLgrSeq, 91442944);
1689
1690 view.rawInsert(sle);
1691 return true;
1692 };
1693 env.app().openLedger().modify(nUNL);
1694 }
1695
1696 Json::Value jvParams;
1697 jvParams[jss::nunl] = to_string(keylet.key);
1698 Json::Value const jrr = env.rpc(
1699 "json", "ledger_entry", to_string(jvParams))[jss::result];
1700 BEAST_EXPECT(
1701 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::NegativeUNL);
1702 }
1703
1704 // negative tests
1706 env,
1707 Json::Value{},
1708 jss::nunl,
1710 "malformedRequest");
1711 }
1712
1713 void
1715 {
1716 testcase("Offer");
1717 using namespace test::jtx;
1718 Env env{*this};
1719 Account const alice{"alice"};
1720 Account const gw{"gateway"};
1721 auto const USD = gw["USD"];
1722 env.fund(XRP(10000), alice, gw);
1723 env.close();
1724
1725 env(offer(alice, USD(321), XRP(322)));
1726 env.close();
1727
1728 std::string const ledgerHash{to_string(env.closed()->header().hash)};
1729 std::string offerIndex;
1730 {
1731 // Request the offer using owner and sequence.
1732 Json::Value jvParams;
1733 jvParams[jss::offer] = Json::objectValue;
1734 jvParams[jss::offer][jss::account] = alice.human();
1735 jvParams[jss::offer][jss::seq] = env.seq(alice) - 1;
1736 jvParams[jss::ledger_hash] = ledgerHash;
1737 Json::Value const jrr = env.rpc(
1738 "json", "ledger_entry", to_string(jvParams))[jss::result];
1739 BEAST_EXPECT(jrr[jss::node][jss::TakerGets] == "322000000");
1740 offerIndex = jrr[jss::index].asString();
1741 }
1742 {
1743 // Request the offer using its index.
1744 Json::Value jvParams;
1745 jvParams[jss::offer] = offerIndex;
1746 Json::Value const jrr = env.rpc(
1747 "json", "ledger_entry", to_string(jvParams))[jss::result];
1748 BEAST_EXPECT(jrr[jss::node][jss::TakerGets] == "322000000");
1749 }
1750
1751 {
1752 // Malformed offer fields
1754 env,
1755 jss::offer,
1756 {{jss::account, "malformedAddress"},
1757 {jss::seq, "malformedRequest"}});
1758 }
1759 }
1760
1761 void
1763 {
1764 testcase("Pay Chan");
1765 using namespace test::jtx;
1766 using namespace std::literals::chrono_literals;
1767 Env env{*this};
1768 Account const alice{"alice"};
1769
1770 env.fund(XRP(10000), alice);
1771 env.close();
1772
1773 // Lambda to create a PayChan.
1774 auto payChanCreate = [](test::jtx::Account const& account,
1775 test::jtx::Account const& to,
1776 STAmount const& amount,
1777 NetClock::duration const& settleDelay,
1778 PublicKey const& pk) {
1779 Json::Value jv;
1780 jv[jss::TransactionType] = jss::PaymentChannelCreate;
1781 jv[jss::Account] = account.human();
1782 jv[jss::Destination] = to.human();
1783 jv[jss::Amount] = amount.getJson(JsonOptions::none);
1784 jv[sfSettleDelay.jsonName] = settleDelay.count();
1785 jv[sfPublicKey.jsonName] = strHex(pk.slice());
1786 return jv;
1787 };
1788
1789 env(payChanCreate(alice, env.master, XRP(57), 18s, alice.pk()));
1790 env.close();
1791
1792 std::string const ledgerHash{to_string(env.closed()->header().hash)};
1793
1794 uint256 const payChanIndex{
1795 keylet::payChan(alice, env.master, env.seq(alice) - 1).key};
1796 {
1797 // Request the payment channel using its index.
1798 Json::Value jvParams;
1799 jvParams[jss::payment_channel] = to_string(payChanIndex);
1800 jvParams[jss::ledger_hash] = ledgerHash;
1801 Json::Value const jrr = env.rpc(
1802 "json", "ledger_entry", to_string(jvParams))[jss::result];
1803 BEAST_EXPECT(jrr[jss::node][sfAmount.jsonName] == "57000000");
1804 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "0");
1805 BEAST_EXPECT(jrr[jss::node][sfSettleDelay.jsonName] == 18);
1806 }
1807 {
1808 // Request an index that is not a payment channel.
1809 Json::Value jvParams;
1810 jvParams[jss::payment_channel] = ledgerHash;
1811 jvParams[jss::ledger_hash] = ledgerHash;
1812 Json::Value const jrr = env.rpc(
1813 "json", "ledger_entry", to_string(jvParams))[jss::result];
1814 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
1815 }
1816
1817 {
1818 // Malformed paychan field
1819 runLedgerEntryTest(env, jss::payment_channel);
1820 }
1821 }
1822
1823 void
1825 {
1826 testcase("RippleState");
1827 using namespace test::jtx;
1828 Env env{*this};
1829 Account const alice{"alice"};
1830 Account const gw{"gateway"};
1831 auto const USD = gw["USD"];
1832 env.fund(XRP(10000), alice, gw);
1833 env.close();
1834
1835 env.trust(USD(999), alice);
1836 env.close();
1837
1838 env(pay(gw, alice, USD(97)));
1839 env.close();
1840
1841 // check both aliases
1842 for (auto const& fieldName : {jss::ripple_state, jss::state})
1843 {
1844 std::string const ledgerHash{
1845 to_string(env.closed()->header().hash)};
1846 {
1847 // Request the trust line using the accounts and currency.
1848 Json::Value jvParams;
1849 jvParams[fieldName] = Json::objectValue;
1850 jvParams[fieldName][jss::accounts] = Json::arrayValue;
1851 jvParams[fieldName][jss::accounts][0u] = alice.human();
1852 jvParams[fieldName][jss::accounts][1u] = gw.human();
1853 jvParams[fieldName][jss::currency] = "USD";
1854 jvParams[jss::ledger_hash] = ledgerHash;
1855 Json::Value const jrr = env.rpc(
1856 "json", "ledger_entry", to_string(jvParams))[jss::result];
1857 BEAST_EXPECT(
1858 jrr[jss::node][sfBalance.jsonName][jss::value] == "-97");
1859 BEAST_EXPECT(
1860 jrr[jss::node][sfHighLimit.jsonName][jss::value] == "999");
1861 }
1862 {
1863 // test basic malformed scenarios
1865 env,
1866 fieldName,
1867 {
1868 {jss::accounts, "malformedRequest"},
1869 {jss::currency, "malformedCurrency"},
1870 });
1871 }
1872 {
1873 // ripple_state one of the accounts is missing.
1874 Json::Value jvParams;
1875 jvParams[fieldName] = Json::objectValue;
1876 jvParams[fieldName][jss::accounts] = Json::arrayValue;
1877 jvParams[fieldName][jss::accounts][0u] = alice.human();
1878 jvParams[fieldName][jss::currency] = "USD";
1879 jvParams[jss::ledger_hash] = ledgerHash;
1880 Json::Value const jrr = env.rpc(
1881 "json", "ledger_entry", to_string(jvParams))[jss::result];
1883 jrr,
1884 "malformedRequest",
1885 "Invalid field 'accounts', not length-2 array of "
1886 "Accounts.");
1887 }
1888 {
1889 // ripple_state more than 2 accounts.
1890 Json::Value jvParams;
1891 jvParams[fieldName] = Json::objectValue;
1892 jvParams[fieldName][jss::accounts] = Json::arrayValue;
1893 jvParams[fieldName][jss::accounts][0u] = alice.human();
1894 jvParams[fieldName][jss::accounts][1u] = gw.human();
1895 jvParams[fieldName][jss::accounts][2u] = alice.human();
1896 jvParams[fieldName][jss::currency] = "USD";
1897 jvParams[jss::ledger_hash] = ledgerHash;
1898 Json::Value const jrr = env.rpc(
1899 "json", "ledger_entry", to_string(jvParams))[jss::result];
1901 jrr,
1902 "malformedRequest",
1903 "Invalid field 'accounts', not length-2 array of "
1904 "Accounts.");
1905 }
1906 {
1907 // ripple_state account[0] / account[1] is not an account.
1908 Json::Value jvParams;
1909 jvParams[fieldName] = Json::objectValue;
1910 auto tryField = [&](Json::Value badAccount) -> void {
1911 {
1912 // account[0]
1913 jvParams[fieldName][jss::accounts] = Json::arrayValue;
1914 jvParams[fieldName][jss::accounts][0u] = badAccount;
1915 jvParams[fieldName][jss::accounts][1u] = gw.human();
1916 jvParams[fieldName][jss::currency] = "USD";
1917
1918 Json::Value const jrr = env.rpc(
1919 "json",
1920 "ledger_entry",
1921 to_string(jvParams))[jss::result];
1923 jrr,
1924 "malformedAddress",
1926 jss::accounts, "array of Accounts"));
1927 }
1928
1929 {
1930 // account[1]
1931 jvParams[fieldName][jss::accounts] = Json::arrayValue;
1932 jvParams[fieldName][jss::accounts][0u] = alice.human();
1933 jvParams[fieldName][jss::accounts][1u] = badAccount;
1934 jvParams[fieldName][jss::currency] = "USD";
1935
1936 Json::Value const jrr = env.rpc(
1937 "json",
1938 "ledger_entry",
1939 to_string(jvParams))[jss::result];
1941 jrr,
1942 "malformedAddress",
1944 jss::accounts, "array of Accounts"));
1945 }
1946 };
1947
1948 auto const& badValues = getBadValues(FieldType::AccountField);
1949 for (auto const& value : badValues)
1950 {
1951 tryField(value);
1952 }
1953 tryField(Json::nullValue);
1954 }
1955 {
1956 // ripple_state account[0] == account[1].
1957 Json::Value jvParams;
1958 jvParams[fieldName] = Json::objectValue;
1959 jvParams[fieldName][jss::accounts] = Json::arrayValue;
1960 jvParams[fieldName][jss::accounts][0u] = alice.human();
1961 jvParams[fieldName][jss::accounts][1u] = alice.human();
1962 jvParams[fieldName][jss::currency] = "USD";
1963 jvParams[jss::ledger_hash] = ledgerHash;
1964 Json::Value const jrr = env.rpc(
1965 "json", "ledger_entry", to_string(jvParams))[jss::result];
1967 jrr,
1968 "malformedRequest",
1969 "Cannot have a trustline to self.");
1970 }
1971 }
1972 }
1973
1974 void
1976 {
1977 testcase("Signer List");
1978 using namespace test::jtx;
1979 Env env{*this};
1980 runLedgerEntryTest(env, jss::signer_list);
1981 }
1982
1983 void
1985 {
1986 testcase("Ticket");
1987 using namespace test::jtx;
1988 Env env{*this};
1989 env.close();
1990
1991 // Create two tickets.
1992 std::uint32_t const tkt1{env.seq(env.master) + 1};
1993 env(ticket::create(env.master, 2));
1994 env.close();
1995
1996 std::string const ledgerHash{to_string(env.closed()->header().hash)};
1997 // Request four tickets: one before the first one we created, the
1998 // two created tickets, and the ticket that would come after the
1999 // last created ticket.
2000 {
2001 // Not a valid ticket requested by index.
2002 Json::Value jvParams;
2003 jvParams[jss::ticket] =
2004 to_string(getTicketIndex(env.master, tkt1 - 1));
2005 jvParams[jss::ledger_hash] = ledgerHash;
2006 Json::Value const jrr = env.rpc(
2007 "json", "ledger_entry", to_string(jvParams))[jss::result];
2008 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
2009 }
2010 {
2011 // First real ticket requested by index.
2012 Json::Value jvParams;
2013 jvParams[jss::ticket] = to_string(getTicketIndex(env.master, tkt1));
2014 jvParams[jss::ledger_hash] = ledgerHash;
2015 Json::Value const jrr = env.rpc(
2016 "json", "ledger_entry", to_string(jvParams))[jss::result];
2017 BEAST_EXPECT(
2018 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Ticket);
2019 BEAST_EXPECT(jrr[jss::node][sfTicketSequence.jsonName] == tkt1);
2020 }
2021 {
2022 // Second real ticket requested by account and sequence.
2023 Json::Value jvParams;
2024 jvParams[jss::ticket] = Json::objectValue;
2025 jvParams[jss::ticket][jss::account] = env.master.human();
2026 jvParams[jss::ticket][jss::ticket_seq] = tkt1 + 1;
2027 jvParams[jss::ledger_hash] = ledgerHash;
2028 Json::Value const jrr = env.rpc(
2029 "json", "ledger_entry", to_string(jvParams))[jss::result];
2030 BEAST_EXPECT(
2031 jrr[jss::node][jss::index] ==
2032 to_string(getTicketIndex(env.master, tkt1 + 1)));
2033 }
2034 {
2035 // Not a valid ticket requested by account and sequence.
2036 Json::Value jvParams;
2037 jvParams[jss::ticket] = Json::objectValue;
2038 jvParams[jss::ticket][jss::account] = env.master.human();
2039 jvParams[jss::ticket][jss::ticket_seq] = tkt1 + 2;
2040 jvParams[jss::ledger_hash] = ledgerHash;
2041 Json::Value const jrr = env.rpc(
2042 "json", "ledger_entry", to_string(jvParams))[jss::result];
2043 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
2044 }
2045 {
2046 // Request a ticket using an account root entry.
2047 Json::Value jvParams;
2048 jvParams[jss::ticket] = to_string(keylet::account(env.master).key);
2049 jvParams[jss::ledger_hash] = ledgerHash;
2050 Json::Value const jrr = env.rpc(
2051 "json", "ledger_entry", to_string(jvParams))[jss::result];
2053 jrr, "unexpectedLedgerType", "Unexpected ledger type.");
2054 }
2055
2056 {
2057 // test basic malformed scenarios
2059 env,
2060 jss::ticket,
2061 {
2062 {jss::account, "malformedAddress"},
2063 {jss::ticket_seq, "malformedRequest"},
2064 });
2065 }
2066 }
2067
2068 void
2070 {
2071 testcase("DID");
2072 using namespace test::jtx;
2073 using namespace std::literals::chrono_literals;
2074 Env env{*this};
2075 Account const alice{"alice"};
2076
2077 env.fund(XRP(10000), alice);
2078 env.close();
2079
2080 // Lambda to create a DID.
2081 auto didCreate = [](test::jtx::Account const& account) {
2082 Json::Value jv;
2083 jv[jss::TransactionType] = jss::DIDSet;
2084 jv[jss::Account] = account.human();
2085 jv[sfDIDDocument.jsonName] = strHex(std::string{"data"});
2086 jv[sfURI.jsonName] = strHex(std::string{"uri"});
2087 return jv;
2088 };
2089
2090 env(didCreate(alice));
2091 env.close();
2092
2093 std::string const ledgerHash{to_string(env.closed()->header().hash)};
2094
2095 {
2096 // Request the DID using its index.
2097 Json::Value jvParams;
2098 jvParams[jss::did] = alice.human();
2099 jvParams[jss::ledger_hash] = ledgerHash;
2100 Json::Value const jrr = env.rpc(
2101 "json", "ledger_entry", to_string(jvParams))[jss::result];
2102 BEAST_EXPECT(
2103 jrr[jss::node][sfDIDDocument.jsonName] ==
2104 strHex(std::string{"data"}));
2105 BEAST_EXPECT(
2106 jrr[jss::node][sfURI.jsonName] == strHex(std::string{"uri"}));
2107 }
2108 {
2109 // Request an index that is not a DID.
2110 Json::Value jvParams;
2111 jvParams[jss::did] = env.master.human();
2112 jvParams[jss::ledger_hash] = ledgerHash;
2113 Json::Value const jrr = env.rpc(
2114 "json", "ledger_entry", to_string(jvParams))[jss::result];
2115 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
2116 }
2117 {
2118 // Malformed DID index
2119 Json::Value jvParams;
2121 env,
2122 jvParams,
2123 jss::did,
2125 "malformedAddress");
2126 }
2127 }
2128
2129 void
2131 {
2132 testcase("Invalid Oracle Ledger Entry");
2133 using namespace xrpl::test::jtx;
2134 using namespace xrpl::test::jtx::oracle;
2135
2136 Env env(*this);
2137 Account const owner("owner");
2138 env.fund(XRP(1'000), owner);
2139 Oracle oracle(
2140 env,
2141 {.owner = owner,
2142 .fee = static_cast<int>(env.current()->fees().base.drops())});
2143
2144 {
2145 // test basic malformed scenarios
2147 env,
2148 jss::oracle,
2149 {
2150 {jss::account, "malformedAccount"},
2151 {jss::oracle_document_id, "malformedDocumentID"},
2152 });
2153 }
2154 }
2155
2156 void
2158 {
2159 testcase("Oracle Ledger Entry");
2160 using namespace xrpl::test::jtx;
2161 using namespace xrpl::test::jtx::oracle;
2162
2163 Env env(*this);
2164 auto const baseFee =
2165 static_cast<int>(env.current()->fees().base.drops());
2166 std::vector<AccountID> accounts;
2168 for (int i = 0; i < 10; ++i)
2169 {
2170 Account const owner(std::string("owner") + std::to_string(i));
2171 env.fund(XRP(1'000), owner);
2172 // different accounts can have the same asset pair
2173 Oracle oracle(
2174 env, {.owner = owner, .documentID = i, .fee = baseFee});
2175 accounts.push_back(owner.id());
2176 oracles.push_back(oracle.documentID());
2177 // same account can have different asset pair
2178 Oracle oracle1(
2179 env, {.owner = owner, .documentID = i + 10, .fee = baseFee});
2180 accounts.push_back(owner.id());
2181 oracles.push_back(oracle1.documentID());
2182 }
2183 for (int i = 0; i < accounts.size(); ++i)
2184 {
2185 auto const jv = [&]() {
2186 // document id is uint32
2187 if (i % 2)
2188 return Oracle::ledgerEntry(env, accounts[i], oracles[i]);
2189 // document id is string
2190 return Oracle::ledgerEntry(
2191 env, accounts[i], std::to_string(oracles[i]));
2192 }();
2193 try
2194 {
2195 BEAST_EXPECT(
2196 jv[jss::node][jss::Owner] == to_string(accounts[i]));
2197 }
2198 catch (...)
2199 {
2200 fail();
2201 }
2202 }
2203 }
2204
2205 void
2207 {
2208 testcase("MPT");
2209 using namespace test::jtx;
2210 using namespace std::literals::chrono_literals;
2211 Env env{*this};
2212 Account const alice{"alice"};
2213 Account const bob("bob");
2214
2215 MPTTester mptAlice(env, alice, {.holders = {bob}});
2216 mptAlice.create(
2217 {.transferFee = 10,
2218 .metadata = "123",
2219 .ownerCount = 1,
2222 mptAlice.authorize({.account = bob, .holderCount = 1});
2223
2224 std::string const ledgerHash{to_string(env.closed()->header().hash)};
2225
2226 std::string const badMptID =
2227 "00000193B9DDCAF401B5B3B26875986043F82CD0D13B4315";
2228 {
2229 // Request the MPTIssuance using its MPTIssuanceID.
2230 Json::Value jvParams;
2231 jvParams[jss::mpt_issuance] = strHex(mptAlice.issuanceID());
2232 jvParams[jss::ledger_hash] = ledgerHash;
2233 Json::Value const jrr = env.rpc(
2234 "json", "ledger_entry", to_string(jvParams))[jss::result];
2235 BEAST_EXPECT(
2236 jrr[jss::node][sfMPTokenMetadata.jsonName] ==
2237 strHex(std::string{"123"}));
2238 BEAST_EXPECT(
2239 jrr[jss::node][jss::mpt_issuance_id] ==
2240 strHex(mptAlice.issuanceID()));
2241 }
2242 {
2243 // Request an index that is not a MPTIssuance.
2244 Json::Value jvParams;
2245 jvParams[jss::mpt_issuance] = badMptID;
2246 jvParams[jss::ledger_hash] = ledgerHash;
2247 Json::Value const jrr = env.rpc(
2248 "json", "ledger_entry", to_string(jvParams))[jss::result];
2249 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
2250 }
2251 {
2252 // Request the MPToken using its owner + mptIssuanceID.
2253 Json::Value jvParams;
2254 jvParams[jss::mptoken] = Json::objectValue;
2255 jvParams[jss::mptoken][jss::account] = bob.human();
2256 jvParams[jss::mptoken][jss::mpt_issuance_id] =
2257 strHex(mptAlice.issuanceID());
2258 jvParams[jss::ledger_hash] = ledgerHash;
2259 Json::Value const jrr = env.rpc(
2260 "json", "ledger_entry", to_string(jvParams))[jss::result];
2261 BEAST_EXPECT(
2262 jrr[jss::node][sfMPTokenIssuanceID.jsonName] ==
2263 strHex(mptAlice.issuanceID()));
2264 }
2265 {
2266 // Request the MPToken using a bad mptIssuanceID.
2267 Json::Value jvParams;
2268 jvParams[jss::mptoken] = Json::objectValue;
2269 jvParams[jss::mptoken][jss::account] = bob.human();
2270 jvParams[jss::mptoken][jss::mpt_issuance_id] = badMptID;
2271 jvParams[jss::ledger_hash] = ledgerHash;
2272 Json::Value const jrr = env.rpc(
2273 "json", "ledger_entry", to_string(jvParams))[jss::result];
2274 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
2275 }
2276 {
2277 // Malformed MPTIssuance index
2278 Json::Value jvParams;
2280 env,
2281 jvParams,
2282 jss::mptoken,
2284 "malformedRequest");
2285 }
2286 }
2287
2288 void
2290 {
2291 testcase("PermissionedDomain");
2292
2293 using namespace test::jtx;
2294
2295 Env env(*this, testable_amendments() | featurePermissionedDomains);
2296 Account const issuer{"issuer"};
2297 Account const alice{"alice"};
2298 Account const bob{"bob"};
2299
2300 env.fund(XRP(5000), issuer, alice, bob);
2301 env.close();
2302
2303 auto const seq = env.seq(alice);
2304 env(pdomain::setTx(alice, {{alice, "first credential"}}));
2305 env.close();
2306 auto const objects = pdomain::getObjects(alice, env);
2307 if (!BEAST_EXPECT(objects.size() == 1))
2308 return;
2309
2310 {
2311 // Succeed
2312 Json::Value params;
2313 params[jss::ledger_index] = jss::validated;
2314 params[jss::permissioned_domain][jss::account] = alice.human();
2315 params[jss::permissioned_domain][jss::seq] = seq;
2316 auto jv = env.rpc("json", "ledger_entry", to_string(params));
2317 BEAST_EXPECT(
2318 jv.isObject() && jv.isMember(jss::result) &&
2319 !jv[jss::result].isMember(jss::error) &&
2320 jv[jss::result].isMember(jss::node) &&
2321 jv[jss::result][jss::node].isMember(
2322 sfLedgerEntryType.jsonName) &&
2323 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
2324 jss::PermissionedDomain);
2325
2326 std::string const pdIdx = jv[jss::result][jss::index].asString();
2327 BEAST_EXPECT(
2328 strHex(keylet::permissionedDomain(alice, seq).key) == pdIdx);
2329
2330 params.clear();
2331 params[jss::ledger_index] = jss::validated;
2332 params[jss::permissioned_domain] = pdIdx;
2333 jv = env.rpc("json", "ledger_entry", to_string(params));
2334 BEAST_EXPECT(
2335 jv.isObject() && jv.isMember(jss::result) &&
2336 !jv[jss::result].isMember(jss::error) &&
2337 jv[jss::result].isMember(jss::node) &&
2338 jv[jss::result][jss::node].isMember(
2339 sfLedgerEntryType.jsonName) &&
2340 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
2341 jss::PermissionedDomain);
2342 }
2343
2344 {
2345 // Fail, invalid permissioned domain index
2346 Json::Value params;
2347 params[jss::ledger_index] = jss::validated;
2348 params[jss::permissioned_domain] =
2349 "12F1F1F1F180D67377B2FAB292A31C922470326268D2B9B74CD1E582645B9A"
2350 "DE";
2351 auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
2353 jrr[jss::result], "entryNotFound", "Entry not found.");
2354 }
2355 {
2356 // test basic malformed scenarios
2358 env,
2359 jss::permissioned_domain,
2360 {
2361 {jss::account, "malformedAddress"},
2362 {jss::seq, "malformedRequest"},
2363 });
2364 }
2365 }
2366
2368 void
2370 {
2371 using namespace test::jtx;
2372
2373 Account const alice{"alice"};
2374 Account const bob{"bob"};
2375
2376 Env env{*this, envconfig([](auto cfg) {
2377 cfg->START_UP = Config::FRESH;
2378 return cfg;
2379 })};
2380
2381 env.close();
2382
2393 auto checkResult =
2394 [&](bool good,
2395 Json::Value const& jv,
2396 Json::StaticString const& expectedType,
2397 std::optional<std::string> const& expectedError = {}) {
2398 if (good)
2399 {
2400 BEAST_EXPECTS(
2401 jv.isObject() && jv.isMember(jss::result) &&
2402 !jv[jss::result].isMember(jss::error) &&
2403 jv[jss::result].isMember(jss::node) &&
2404 jv[jss::result][jss::node].isMember(
2405 sfLedgerEntryType.jsonName) &&
2406 jv[jss::result][jss::node]
2407 [sfLedgerEntryType.jsonName] == expectedType,
2408 to_string(jv));
2409 }
2410 else
2411 {
2412 BEAST_EXPECTS(
2413 jv.isObject() && jv.isMember(jss::result) &&
2414 jv[jss::result].isMember(jss::error) &&
2415 !jv[jss::result].isMember(jss::node) &&
2416 jv[jss::result][jss::error] ==
2417 expectedError.value_or("entryNotFound"),
2418 to_string(jv));
2419 }
2420 };
2421
2432 auto test = [&](Json::StaticString const& field,
2433 Json::StaticString const& expectedType,
2434 Keylet const& expectedKey,
2435 bool good) {
2436 testcase << expectedType.c_str() << (good ? "" : " not")
2437 << " found";
2438
2439 auto const hexKey = strHex(expectedKey.key);
2440
2441 {
2442 // Test bad values
2443 // "field":null
2444 Json::Value params;
2445 params[jss::ledger_index] = jss::validated;
2446 params[field] = Json::nullValue;
2447 auto const jv =
2448 env.rpc("json", "ledger_entry", to_string(params));
2449 checkResult(false, jv, expectedType, "malformedRequest");
2450 BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
2451 }
2452
2453 {
2454 Json::Value params;
2455 // "field":"string"
2456 params[jss::ledger_index] = jss::validated;
2457 params[field] = "arbitrary string";
2458 auto const jv =
2459 env.rpc("json", "ledger_entry", to_string(params));
2460 checkResult(false, jv, expectedType, "malformedRequest");
2461 BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
2462 }
2463
2464 {
2465 Json::Value params;
2466 // "field":false
2467 params[jss::ledger_index] = jss::validated;
2468 params[field] = false;
2469 auto const jv =
2470 env.rpc("json", "ledger_entry", to_string(params));
2471 checkResult(false, jv, expectedType, "invalidParams");
2472 BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
2473 }
2474
2475 {
2476 Json::Value params;
2477
2478 // "field":[incorrect index hash]
2479 auto const badKey = strHex(expectedKey.key + uint256{1});
2480 params[jss::ledger_index] = jss::validated;
2481 params[field] = badKey;
2482 auto const jv =
2483 env.rpc("json", "ledger_entry", to_string(params));
2484 checkResult(false, jv, expectedType, "entryNotFound");
2485 BEAST_EXPECTS(
2486 jv[jss::result][jss::index] == badKey, to_string(jv));
2487 }
2488
2489 {
2490 Json::Value params;
2491 // "index":"field" using API 2
2492 params[jss::ledger_index] = jss::validated;
2493 params[jss::index] = field;
2494 params[jss::api_version] = 2;
2495 auto const jv =
2496 env.rpc("json", "ledger_entry", to_string(params));
2497 checkResult(false, jv, expectedType, "malformedRequest");
2498 BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
2499 }
2500
2501 std::string const pdIdx = [&]() {
2502 {
2503 Json::Value params;
2504 // Test good values
2505 // Use the "field":true notation
2506 params[jss::ledger_index] = jss::validated;
2507 params[field] = true;
2508 auto const jv =
2509 env.rpc("json", "ledger_entry", to_string(params));
2510 // Index will always be returned for valid parameters.
2511 std::string const pdIdx =
2512 jv[jss::result][jss::index].asString();
2513 BEAST_EXPECTS(hexKey == pdIdx, to_string(jv));
2514 checkResult(good, jv, expectedType);
2515
2516 return pdIdx;
2517 }
2518 }();
2519
2520 {
2521 Json::Value params;
2522 // "field":"[index hash]"
2523 params[jss::ledger_index] = jss::validated;
2524 params[field] = hexKey;
2525 auto const jv =
2526 env.rpc("json", "ledger_entry", to_string(params));
2527 checkResult(good, jv, expectedType);
2528 BEAST_EXPECT(jv[jss::result][jss::index].asString() == hexKey);
2529 }
2530
2531 {
2532 // Bad value
2533 // Use the "index":"field" notation with API 2
2534 Json::Value params;
2535 params[jss::ledger_index] = jss::validated;
2536 params[jss::index] = field;
2537 params[jss::api_version] = 2;
2538 auto const jv =
2539 env.rpc("json", "ledger_entry", to_string(params));
2540 checkResult(false, jv, expectedType, "malformedRequest");
2541 BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
2542 }
2543
2544 {
2545 Json::Value params;
2546 // Use the "index":"field" notation with API 3
2547 params[jss::ledger_index] = jss::validated;
2548 params[jss::index] = field;
2549 params[jss::api_version] = 3;
2550 auto const jv =
2551 env.rpc("json", "ledger_entry", to_string(params));
2552 // Index is correct either way
2553 BEAST_EXPECT(jv[jss::result][jss::index].asString() == hexKey);
2554 checkResult(good, jv, expectedType);
2555 }
2556
2557 {
2558 Json::Value params;
2559 // Use the "index":"[index hash]" notation
2560 params[jss::ledger_index] = jss::validated;
2561 params[jss::index] = pdIdx;
2562 auto const jv =
2563 env.rpc("json", "ledger_entry", to_string(params));
2564 // Index is correct either way
2565 BEAST_EXPECT(jv[jss::result][jss::index].asString() == hexKey);
2566 checkResult(good, jv, expectedType);
2567 }
2568 };
2569
2570 test(jss::amendments, jss::Amendments, keylet::amendments(), true);
2571 test(jss::fee, jss::FeeSettings, keylet::fees(), true);
2572 // There won't be an nunl
2573 test(jss::nunl, jss::NegativeUNL, keylet::negativeUNL(), false);
2574 // Can only get the short skip list this way
2575 test(jss::hashes, jss::LedgerHashes, keylet::skip(), true);
2576 }
2577
2578 void
2580 {
2581 using namespace test::jtx;
2582
2583 Account const alice{"alice"};
2584 Account const bob{"bob"};
2585
2586 Env env{*this, envconfig([](auto cfg) {
2587 cfg->START_UP = Config::FRESH;
2588 return cfg;
2589 })};
2590
2591 env.close();
2592
2603 auto checkResult =
2604 [&](bool good,
2605 Json::Value const& jv,
2606 int expectedCount,
2607 std::optional<std::string> const& expectedError = {}) {
2608 if (good)
2609 {
2610 BEAST_EXPECTS(
2611 jv.isObject() && jv.isMember(jss::result) &&
2612 !jv[jss::result].isMember(jss::error) &&
2613 jv[jss::result].isMember(jss::node) &&
2614 jv[jss::result][jss::node].isMember(
2615 sfLedgerEntryType.jsonName) &&
2616 jv[jss::result][jss::node]
2617 [sfLedgerEntryType.jsonName] == jss::LedgerHashes,
2618 to_string(jv));
2619 BEAST_EXPECTS(
2620 jv[jss::result].isMember(jss::node) &&
2621 jv[jss::result][jss::node].isMember("Hashes") &&
2622 jv[jss::result][jss::node]["Hashes"].size() ==
2623 expectedCount,
2624 to_string(jv[jss::result][jss::node]["Hashes"].size()));
2625 }
2626 else
2627 {
2628 BEAST_EXPECTS(
2629 jv.isObject() && jv.isMember(jss::result) &&
2630 jv[jss::result].isMember(jss::error) &&
2631 !jv[jss::result].isMember(jss::node) &&
2632 jv[jss::result][jss::error] ==
2633 expectedError.value_or("entryNotFound"),
2634 to_string(jv));
2635 }
2636 };
2637
2648 auto test = [&](Json::Value ledger,
2649 Keylet const& expectedKey,
2650 bool good,
2651 int expectedCount = 0) {
2652 testcase << "LedgerHashes: seq: " << env.current()->header().seq
2653 << " \"hashes\":" << to_string(ledger)
2654 << (good ? "" : " not") << " found";
2655
2656 auto const hexKey = strHex(expectedKey.key);
2657
2658 {
2659 // Test bad values
2660 // "hashes":null
2661 Json::Value params;
2662 params[jss::ledger_index] = jss::validated;
2663 params[jss::hashes] = Json::nullValue;
2664 auto jv = env.rpc("json", "ledger_entry", to_string(params));
2665 checkResult(false, jv, 0, "malformedRequest");
2666 BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
2667 }
2668
2669 {
2670 Json::Value params;
2671 // "hashes":"non-uint string"
2672 params[jss::ledger_index] = jss::validated;
2673 params[jss::hashes] = "arbitrary string";
2674 auto const jv =
2675 env.rpc("json", "ledger_entry", to_string(params));
2676 checkResult(false, jv, 0, "malformedRequest");
2677 BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
2678 }
2679
2680 {
2681 Json::Value params;
2682 // "hashes":"uint string" is invalid, too
2683 params[jss::ledger_index] = jss::validated;
2684 params[jss::hashes] = "10";
2685 auto const jv =
2686 env.rpc("json", "ledger_entry", to_string(params));
2687 checkResult(false, jv, 0, "malformedRequest");
2688 BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
2689 }
2690
2691 {
2692 Json::Value params;
2693 // "hashes":false
2694 params[jss::ledger_index] = jss::validated;
2695 params[jss::hashes] = false;
2696 auto const jv =
2697 env.rpc("json", "ledger_entry", to_string(params));
2698 checkResult(false, jv, 0, "invalidParams");
2699 BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
2700 }
2701
2702 {
2703 Json::Value params;
2704 // "hashes":-1
2705 params[jss::ledger_index] = jss::validated;
2706 params[jss::hashes] = -1;
2707 auto const jv =
2708 env.rpc("json", "ledger_entry", to_string(params));
2709 checkResult(false, jv, 0, "internal");
2710 BEAST_EXPECT(!jv[jss::result].isMember(jss::index));
2711 }
2712
2713 // "hashes":[incorrect index hash]
2714 {
2715 Json::Value params;
2716 auto const badKey = strHex(expectedKey.key + uint256{1});
2717 params[jss::ledger_index] = jss::validated;
2718 params[jss::hashes] = badKey;
2719 auto const jv =
2720 env.rpc("json", "ledger_entry", to_string(params));
2721 checkResult(false, jv, 0, "entryNotFound");
2722 BEAST_EXPECT(jv[jss::result][jss::index] == badKey);
2723 }
2724
2725 {
2726 Json::Value params;
2727 // Test good values
2728 // Use the "hashes":ledger notation
2729 params[jss::ledger_index] = jss::validated;
2730 params[jss::hashes] = ledger;
2731 auto const jv =
2732 env.rpc("json", "ledger_entry", to_string(params));
2733 checkResult(good, jv, expectedCount);
2734 // Index will always be returned for valid parameters.
2735 std::string const pdIdx =
2736 jv[jss::result][jss::index].asString();
2737 BEAST_EXPECTS(hexKey == pdIdx, strHex(pdIdx));
2738 }
2739
2740 {
2741 Json::Value params;
2742 // "hashes":"[index hash]"
2743 params[jss::ledger_index] = jss::validated;
2744 params[jss::hashes] = hexKey;
2745 auto const jv =
2746 env.rpc("json", "ledger_entry", to_string(params));
2747 checkResult(good, jv, expectedCount);
2748 // Index is correct either way
2749 BEAST_EXPECTS(
2750 hexKey == jv[jss::result][jss::index].asString(),
2751 strHex(jv[jss::result][jss::index].asString()));
2752 }
2753
2754 {
2755 Json::Value params;
2756 // Use the "index":"[index hash]" notation
2757 params[jss::ledger_index] = jss::validated;
2758 params[jss::index] = hexKey;
2759 auto const jv =
2760 env.rpc("json", "ledger_entry", to_string(params));
2761 checkResult(good, jv, expectedCount);
2762 // Index is correct either way
2763 BEAST_EXPECTS(
2764 hexKey == jv[jss::result][jss::index].asString(),
2765 strHex(jv[jss::result][jss::index].asString()));
2766 }
2767 };
2768
2769 // short skip list
2770 test(true, keylet::skip(), true, 2);
2771 // long skip list at index 0
2772 test(1, keylet::skip(1), false);
2773 // long skip list at index 1
2774 test(1 << 17, keylet::skip(1 << 17), false);
2775
2776 // Close more ledgers, but stop short of the flag ledger
2777 for (auto i = env.current()->seq(); i <= 250; ++i)
2778 env.close();
2779
2780 // short skip list
2781 test(true, keylet::skip(), true, 249);
2782 // long skip list at index 0
2783 test(1, keylet::skip(1), false);
2784 // long skip list at index 1
2785 test(1 << 17, keylet::skip(1 << 17), false);
2786
2787 // Close a flag ledger so the first "long" skip list is created
2788 for (auto i = env.current()->seq(); i <= 260; ++i)
2789 env.close();
2790
2791 // short skip list
2792 test(true, keylet::skip(), true, 256);
2793 // long skip list at index 0
2794 test(1, keylet::skip(1), true, 1);
2795 // long skip list at index 1
2796 test(1 << 17, keylet::skip(1 << 17), false);
2797 }
2798
2799 void
2801 {
2802 testcase("command-line");
2803 using namespace test::jtx;
2804
2805 Env env{*this};
2806 Account const alice{"alice"};
2807 env.fund(XRP(10000), alice);
2808 env.close();
2809
2810 auto const checkId = keylet::check(env.master, env.seq(env.master));
2811
2812 env(check::create(env.master, alice, XRP(100)));
2813 env.close();
2814
2815 std::string const ledgerHash{to_string(env.closed()->header().hash)};
2816 {
2817 // Request a check.
2818 Json::Value const jrr =
2819 env.rpc("ledger_entry", to_string(checkId.key))[jss::result];
2820 BEAST_EXPECT(
2821 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Check);
2822 BEAST_EXPECT(jrr[jss::node][sfSendMax.jsonName] == "100000000");
2823 }
2824 }
2825
2826public:
2827 void
2828 run() override
2829 {
2830 testInvalid();
2833 testAMM();
2834 testCheck();
2836 testDelegate();
2839 testDirectory();
2840 testEscrow();
2846 testOffer();
2847 testPayChan();
2850 testTicket();
2851 testDID();
2854 testMPT();
2856 testFixed();
2857 testHashes();
2858 testCLI();
2859 }
2860};
2861
2864{
2865 void
2867 Json::Value const& jv,
2868 std::string const& err,
2869 std::string const& msg)
2870 {
2871 if (BEAST_EXPECT(jv.isMember(jss::status)))
2872 BEAST_EXPECT(jv[jss::status] == "error");
2873 if (BEAST_EXPECT(jv.isMember(jss::error)))
2874 BEAST_EXPECT(jv[jss::error] == err);
2875 if (msg.empty())
2876 {
2877 BEAST_EXPECT(
2878 jv[jss::error_message] == Json::nullValue ||
2879 jv[jss::error_message] == "");
2880 }
2881 else if (BEAST_EXPECT(jv.isMember(jss::error_message)))
2882 BEAST_EXPECT(jv[jss::error_message] == msg);
2883 }
2884
2885 void
2887 {
2888 testcase("ledger_entry: bridge");
2889 using namespace test::jtx;
2890
2891 Env mcEnv{*this, features};
2892 Env scEnv(*this, envconfig(), features);
2893
2894 createBridgeObjects(mcEnv, scEnv);
2895
2896 std::string const ledgerHash{to_string(mcEnv.closed()->header().hash)};
2897 std::string bridge_index;
2898 Json::Value mcBridge;
2899 {
2900 // request the bridge via RPC
2901 Json::Value jvParams;
2902 jvParams[jss::bridge_account] = mcDoor.human();
2903 jvParams[jss::bridge] = jvb;
2904 Json::Value const jrr = mcEnv.rpc(
2905 "json", "ledger_entry", to_string(jvParams))[jss::result];
2906
2907 BEAST_EXPECT(jrr.isMember(jss::node));
2908 auto r = jrr[jss::node];
2909
2910 BEAST_EXPECT(r.isMember(jss::Account));
2911 BEAST_EXPECT(r[jss::Account] == mcDoor.human());
2912
2913 BEAST_EXPECT(r.isMember(jss::Flags));
2914
2915 BEAST_EXPECT(r.isMember(sfLedgerEntryType.jsonName));
2916 BEAST_EXPECT(r[sfLedgerEntryType.jsonName] == jss::Bridge);
2917
2918 // we not created an account yet
2919 BEAST_EXPECT(r.isMember(sfXChainAccountCreateCount.jsonName));
2920 BEAST_EXPECT(r[sfXChainAccountCreateCount.jsonName].asInt() == 0);
2921
2922 // we have not claimed a locking chain tx yet
2923 BEAST_EXPECT(r.isMember(sfXChainAccountClaimCount.jsonName));
2924 BEAST_EXPECT(r[sfXChainAccountClaimCount.jsonName].asInt() == 0);
2925
2926 BEAST_EXPECT(r.isMember(jss::index));
2927 bridge_index = r[jss::index].asString();
2928 mcBridge = r;
2929 }
2930 {
2931 // request the bridge via RPC by index
2932 Json::Value jvParams;
2933 jvParams[jss::index] = bridge_index;
2934 Json::Value const jrr = mcEnv.rpc(
2935 "json", "ledger_entry", to_string(jvParams))[jss::result];
2936
2937 BEAST_EXPECT(jrr.isMember(jss::node));
2938 BEAST_EXPECT(jrr[jss::node] == mcBridge);
2939 }
2940 {
2941 // swap door accounts and make sure we get an error value
2942 Json::Value jvParams;
2943 // Sidechain door account is "master", not scDoor
2944 jvParams[jss::bridge_account] = Account::master.human();
2945 jvParams[jss::bridge] = jvb;
2946 jvParams[jss::ledger_hash] = ledgerHash;
2947 Json::Value const jrr = mcEnv.rpc(
2948 "json", "ledger_entry", to_string(jvParams))[jss::result];
2949
2950 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
2951 }
2952 {
2953 // create two claim ids and verify that the bridge counter was
2954 // incremented
2956 mcEnv.close();
2958 mcEnv.close();
2959
2960 // request the bridge via RPC
2961 Json::Value jvParams;
2962 jvParams[jss::bridge_account] = mcDoor.human();
2963 jvParams[jss::bridge] = jvb;
2964 Json::Value const jrr = mcEnv.rpc(
2965 "json", "ledger_entry", to_string(jvParams))[jss::result];
2966
2967 BEAST_EXPECT(jrr.isMember(jss::node));
2968 auto r = jrr[jss::node];
2969
2970 // we executed two create claim id txs
2971 BEAST_EXPECT(r.isMember(sfXChainClaimID.jsonName));
2972 BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 2);
2973 }
2974 }
2975
2976 void
2978 {
2979 testcase("ledger_entry: xchain_claim_id");
2980 using namespace test::jtx;
2981
2982 Env mcEnv{*this, features};
2983 Env scEnv(*this, envconfig(), features);
2984
2985 createBridgeObjects(mcEnv, scEnv);
2986
2988 scEnv.close();
2990 scEnv.close();
2991
2992 std::string bridge_index;
2993 {
2994 // request the xchain_claim_id via RPC
2995 Json::Value jvParams;
2996 jvParams[jss::xchain_owned_claim_id] = jvXRPBridgeRPC;
2997 jvParams[jss::xchain_owned_claim_id][jss::xchain_owned_claim_id] =
2998 1;
2999 Json::Value const jrr = scEnv.rpc(
3000 "json", "ledger_entry", to_string(jvParams))[jss::result];
3001
3002 BEAST_EXPECT(jrr.isMember(jss::node));
3003 auto r = jrr[jss::node];
3004
3005 BEAST_EXPECT(r.isMember(jss::Account));
3006 BEAST_EXPECT(r[jss::Account] == scAlice.human());
3007 BEAST_EXPECT(
3008 r[sfLedgerEntryType.jsonName] == jss::XChainOwnedClaimID);
3009 BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 1);
3010 BEAST_EXPECT(r[sfOwnerNode.jsonName].asInt() == 0);
3011 }
3012
3013 {
3014 // request the xchain_claim_id via RPC
3015 Json::Value jvParams;
3016 jvParams[jss::xchain_owned_claim_id] = jvXRPBridgeRPC;
3017 jvParams[jss::xchain_owned_claim_id][jss::xchain_owned_claim_id] =
3018 2;
3019 Json::Value const jrr = scEnv.rpc(
3020 "json", "ledger_entry", to_string(jvParams))[jss::result];
3021
3022 BEAST_EXPECT(jrr.isMember(jss::node));
3023 auto r = jrr[jss::node];
3024
3025 BEAST_EXPECT(r.isMember(jss::Account));
3026 BEAST_EXPECT(r[jss::Account] == scBob.human());
3027 BEAST_EXPECT(
3028 r[sfLedgerEntryType.jsonName] == jss::XChainOwnedClaimID);
3029 BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 2);
3030 BEAST_EXPECT(r[sfOwnerNode.jsonName].asInt() == 0);
3031 }
3032 }
3033
3034 void
3036 {
3037 testcase("ledger_entry: xchain_create_account_claim_id");
3038 using namespace test::jtx;
3039
3040 Env mcEnv{*this, features};
3041 Env scEnv(*this, envconfig(), features);
3042
3043 // note: signers.size() and quorum are both 5 in createBridgeObjects
3044 createBridgeObjects(mcEnv, scEnv);
3045
3046 auto scCarol =
3047 Account("scCarol"); // Don't fund it - it will be created with the
3048 // xchain transaction
3049 auto const amt = XRP(1000);
3051 mcAlice, jvb, scCarol, amt, reward));
3052 mcEnv.close();
3053
3054 // send less than quorum of attestations (otherwise funds are
3055 // immediately transferred and no "claim" object is created)
3056 size_t constexpr num_attest = 3;
3057 auto attestations = create_account_attestations(
3058 scAttester,
3059 jvb,
3060 mcAlice,
3061 amt,
3062 reward,
3063 payee,
3064 /*wasLockingChainSend*/ true,
3065 1,
3066 scCarol,
3067 signers,
3069 for (size_t i = 0; i < num_attest; ++i)
3070 {
3071 scEnv(attestations[i]);
3072 }
3073 scEnv.close();
3074
3075 {
3076 // request the create account claim_id via RPC
3077 Json::Value jvParams;
3078 jvParams[jss::xchain_owned_create_account_claim_id] =
3080 jvParams[jss::xchain_owned_create_account_claim_id]
3081 [jss::xchain_owned_create_account_claim_id] = 1;
3082 Json::Value const jrr = scEnv.rpc(
3083 "json", "ledger_entry", to_string(jvParams))[jss::result];
3084
3085 BEAST_EXPECT(jrr.isMember(jss::node));
3086 auto r = jrr[jss::node];
3087
3088 BEAST_EXPECT(r.isMember(jss::Account));
3089 BEAST_EXPECT(r[jss::Account] == Account::master.human());
3090
3091 BEAST_EXPECT(r.isMember(sfXChainAccountCreateCount.jsonName));
3092 BEAST_EXPECT(r[sfXChainAccountCreateCount.jsonName].asInt() == 1);
3093
3094 BEAST_EXPECT(
3095 r.isMember(sfXChainCreateAccountAttestations.jsonName));
3096 auto attest = r[sfXChainCreateAccountAttestations.jsonName];
3097 BEAST_EXPECT(attest.isArray());
3098 BEAST_EXPECT(attest.size() == 3);
3099 BEAST_EXPECT(attest[Json::Value::UInt(0)].isMember(
3100 sfXChainCreateAccountProofSig.jsonName));
3101 Json::Value a[num_attest];
3102 for (size_t i = 0; i < num_attest; ++i)
3103 {
3104 a[i] = attest[Json::Value::UInt(0)]
3105 [sfXChainCreateAccountProofSig.jsonName];
3106 BEAST_EXPECT(
3107 a[i].isMember(jss::Amount) &&
3108 a[i][jss::Amount].asInt() == 1000 * drop_per_xrp);
3109 BEAST_EXPECT(
3110 a[i].isMember(jss::Destination) &&
3111 a[i][jss::Destination] == scCarol.human());
3112 BEAST_EXPECT(
3113 a[i].isMember(sfAttestationSignerAccount.jsonName) &&
3115 signers.begin(), signers.end(), [&](signer const& s) {
3116 return a[i][sfAttestationSignerAccount.jsonName] ==
3117 s.account.human();
3118 }));
3119 BEAST_EXPECT(
3120 a[i].isMember(sfAttestationRewardAccount.jsonName) &&
3122 payee.begin(),
3123 payee.end(),
3124 [&](Account const& account) {
3125 return a[i][sfAttestationRewardAccount.jsonName] ==
3126 account.human();
3127 }));
3128 BEAST_EXPECT(
3129 a[i].isMember(sfWasLockingChainSend.jsonName) &&
3130 a[i][sfWasLockingChainSend.jsonName] == 1);
3131 BEAST_EXPECT(
3132 a[i].isMember(sfSignatureReward.jsonName) &&
3133 a[i][sfSignatureReward.jsonName].asInt() ==
3134 1 * drop_per_xrp);
3135 }
3136 }
3137
3138 // complete attestations quorum - CreateAccountClaimID should not be
3139 // present anymore
3140 for (size_t i = num_attest; i < UT_XCHAIN_DEFAULT_NUM_SIGNERS; ++i)
3141 {
3142 scEnv(attestations[i]);
3143 }
3144 scEnv.close();
3145 {
3146 // request the create account claim_id via RPC
3147 Json::Value jvParams;
3148 jvParams[jss::xchain_owned_create_account_claim_id] =
3150 jvParams[jss::xchain_owned_create_account_claim_id]
3151 [jss::xchain_owned_create_account_claim_id] = 1;
3152 Json::Value const jrr = scEnv.rpc(
3153 "json", "ledger_entry", to_string(jvParams))[jss::result];
3154 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
3155 }
3156 }
3157
3158public:
3159 void
3160 run() override
3161 {
3162 testBridge();
3163 testClaimID();
3165 }
3166};
3167
3168BEAST_DEFINE_TESTSUITE(LedgerEntry, rpc, xrpl);
3169BEAST_DEFINE_TESTSUITE(LedgerEntry_XChain, rpc, xrpl);
3170
3171} // namespace test
3172} // namespace xrpl
T any_of(T... args)
T begin(T... args)
Lightweight wrapper to tag static string.
Definition json_value.h:45
constexpr char const * c_str() const
Definition json_value.h:58
Represents a JSON value.
Definition json_value.h:131
Json::UInt UInt
Definition json_value.h:138
Value & append(Value const &value)
Append value to array at the end.
UInt size() const
Number of values in array or object.
std::string toStyledString() const
void clear()
Remove all object members and array elements.
Int asInt() const
bool isObject() const
Value removeMember(char const *key)
Remove and return the named member.
std::string asString() const
Returns the unquoted string value.
bool isNull() const
isNull() tests to see if this field is null.
bool isMember(char const *key) const
Return true if the object has a member named key.
A generic endpoint for log messages.
Definition Journal.h:41
A testsuite class.
Definition suite.h:52
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:152
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:530
virtual OpenLedger & openLedger()=0
bool modify(modify_type const &f)
Modify the open ledger.
Writable ledger view that accumulates state and tx changes.
Definition OpenView.h:46
void rawInsert(std::shared_ptr< SLE > const &sle) override
Unconditionally insert a state item.
Definition OpenView.cpp:218
A public key.
Definition PublicKey.h:43
void push_back(STObject const &object)
Definition STArray.h:193
static STObject makeInnerObject(SField const &name)
Definition STObject.cpp:76
static base_uint fromVoid(void const *data)
Definition base_uint.h:300
void run() override
Runs the suite.
void checkErrorValue(Json::Value const &jv, std::string const &err, std::string const &msg)
void checkErrorValue(Json::Value const &jv, std::string const &err, std::string const &msg, std::source_location const location=std::source_location::current())
void testMalformedSubfield(test::jtx::Env &env, Json::Value correctRequest, Json::StaticString parentFieldName, Json::StaticString fieldName, FieldType typeID, std::string const &expectedError, bool required=true, std::source_location const location=std::source_location::current())
void run() override
Runs the suite.
void testMalformedField(test::jtx::Env &env, Json::Value correctRequest, Json::StaticString const fieldName, FieldType const typeID, std::string const &expectedError, bool required=true, std::source_location const location=std::source_location::current())
void runLedgerEntryTest(test::jtx::Env &env, Json::StaticString const &parentField, std::source_location const location=std::source_location::current())
void runLedgerEntryTest(test::jtx::Env &env, Json::StaticString const &parentField, std::vector< Subfield > const &subfields, std::source_location const location=std::source_location::current())
void testFixed()
Test the ledger entry types that don't take parameters.
Json::Value getCorrectValue(Json::StaticString fieldName)
std::vector< Json::Value > getBadValues(FieldType fieldType)
Convenience class to test AMM functionality.
Definition AMM.h:105
Immutable cryptographic account descriptor.
Definition Account.h:20
std::string const & human() const
Returns the human readable public key.
Definition Account.h:99
static Account const master
The master account.
Definition Account.h:29
AccountID id() const
Returns the Account ID.
Definition Account.h:92
A transaction testing environment.
Definition Env.h:102
Application & app()
Definition Env.h:244
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:104
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition Env.cpp:98
std::shared_ptr< SLE const > le(Account const &account) const
Return an account root.
Definition Env.cpp:260
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:272
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:251
Account const & master
Definition Env.h:106
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:303
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition Env.h:774
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:314
NetClock::time_point now()
Returns the current network time.
Definition Env.h:267
void create(MPTCreate const &arg=MPTCreate{})
Definition mpt.cpp:144
Oracle class facilitates unit-testing of the Price Oracle feature.
Definition Oracle.h:101
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition rpc.h:16
Sets the optional Destination field on an NFTokenOffer.
Definition token.h:144
Set the flags on a JTx.
Definition txflags.h:12
T current(T... args)
T empty(T... args)
T end(T... args)
T find_if(T... args)
T is_same_v
@ nullValue
'null' value
Definition json_value.h:20
@ arrayValue
array value (ordered list)
Definition json_value.h:26
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:27
std::string expected_field_message(std::string const &name, std::string const &type)
Definition ErrorCodes.h:296
std::string missing_field_message(std::string const &name)
Definition ErrorCodes.h:236
Keylet const & skip() noexcept
The index of the "short" skip list.
Definition Indexes.cpp:178
Keylet const & negativeUNL() noexcept
The (fixed) index of the object containing the ledger negativeUNL.
Definition Indexes.cpp:212
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition Indexes.cpp:393
Keylet nftoffer(AccountID const &owner, std::uint32_t seq)
An offer from an account to buy or sell an NFT.
Definition Indexes.cpp:409
Keylet const & amendments() noexcept
The index of the amendment table.
Definition Indexes.cpp:196
Keylet payChan(AccountID const &src, AccountID const &dst, std::uint32_t seq) noexcept
A PaymentChannel.
Definition Indexes.cpp:377
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition Indexes.cpp:318
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
Keylet const & fees() noexcept
The (fixed) index of the object containing the ledger fees.
Definition Indexes.cpp:204
Keylet permissionedDomain(AccountID const &account, std::uint32_t seq) noexcept
Definition Indexes.cpp:564
Json::Value create(A const &account, A const &dest, STAmount const &sendMax)
Create a check.
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:59
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:13
Json::Value set(jtx::Account const &account, jtx::Account const &authorize, std::vector< std::string > const &permissions)
Definition delegate.cpp:12
Json::Value authCredentials(jtx::Account const &account, std::vector< AuthorizeCredentials > const &auth)
Definition deposit.cpp:35
Json::Value auth(Account const &account, Account const &auth)
Preauthorize for deposit.
Definition deposit.cpp:13
Json::Value setTx(AccountID const &account, Credentials const &credentials, std::optional< uint256 > domain)
std::map< uint256, Json::Value > getObjects(Account const &account, Env &env, bool withType)
Json::Value create(Account const &account, std::uint32_t count)
Create one of more tickets.
Definition ticket.cpp:12
Json::Value mint(jtx::Account const &account, std::uint32_t nfTokenTaxon)
Mint an NFToken.
Definition token.cpp:15
Json::Value createOffer(jtx::Account const &account, uint256 const &nftokenID, STAmount const &amount)
Create an NFTokenOffer.
Definition token.cpp:90
uint256 getNextID(jtx::Env const &env, jtx::Account const &issuer, std::uint32_t nfTokenTaxon, std::uint16_t flags, std::uint16_t xferFee)
Get the next NFTokenID that will be issued.
Definition token.cpp:49
Json::Value sidechain_xchain_account_create(Account const &acc, Json::Value const &bridge, Account const &dst, AnyAmount const &amt, AnyAmount const &reward)
constexpr std::size_t UT_XCHAIN_DEFAULT_NUM_SIGNERS
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
require_t required(Args const &... args)
Compose many condition functors into one.
Definition require.h:30
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:11
JValueVec create_account_attestations(jtx::Account const &submittingAccount, Json::Value const &jvBridge, jtx::Account const &sendingAccount, jtx::AnyAmount const &sendingAmount, jtx::AnyAmount const &rewardAmount, std::vector< jtx::Account > const &rewardAccounts, bool wasLockingChainSend, std::uint64_t createCount, jtx::Account const &dst, std::vector< jtx::signer > const &signers, std::size_t const numAtts, std::size_t const fromIdx)
Json::Value xchain_create_claim_id(Account const &acc, Json::Value const &bridge, STAmount const &reward, Account const &otherChainSource)
FeatureBitset testable_amendments()
Definition Env.h:55
auto const amount
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:35
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:10
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:10
std::vector< std::pair< Json::StaticString, FieldType > > mappings
FieldType getFieldType(Json::StaticString fieldName)
static uint256 ledgerHash(LedgerHeader const &info)
std::string getTypeName(FieldType typeID)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
constexpr std::uint32_t const tfTransferable
Definition TxFlags.h:123
constexpr std::uint32_t const tfMPTCanTransfer
Definition TxFlags.h:133
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:11
constexpr std::uint32_t const tfMPTRequireAuth
Definition TxFlags.h:130
uint256 getTicketIndex(AccountID const &account, std::uint32_t uSequence)
Definition Indexes.cpp:138
constexpr std::uint32_t asfDepositAuth
Definition TxFlags.h:66
constexpr std::uint32_t const tfMPTCanTrade
Definition TxFlags.h:132
constexpr std::uint32_t const tfMPTCanLock
Definition TxFlags.h:129
constexpr std::uint32_t const tfMPTCanClawback
Definition TxFlags.h:134
std::optional< Blob > strUnHex(std::size_t strSize, Iterator begin, Iterator end)
std::size_t constexpr maxCredentialsArraySize
The maximum number of credentials can be passed in array.
Definition Protocol.h:225
void forAllApiVersions(Fn const &fn, Args &&... args)
Definition ApiVersion.h:158
constexpr std::uint32_t const tfSellNFToken
Definition TxFlags.h:211
constexpr std::uint32_t const tfMPTCanEscrow
Definition TxFlags.h:131
T push_back(T... args)
T reserve(T... args)
T size(T... args)
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
uint256 key
Definition Keylet.h:21
void createBridgeObjects(Env &mcEnv, Env &scEnv)
std::vector< Account > const payee
std::vector< signer > const signers
Set the sequence number on a JTx.
Definition seq.h:15
A signer in a SignerList.
Definition multisign.h:20
T to_string(T... args)