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