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