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