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