rippled
Loading...
Searching...
No Matches
LedgerEntry_test.cpp
1#include <test/jtx.h>
2#include <test/jtx/Oracle.h>
3#include <test/jtx/attester.h>
4#include <test/jtx/delegate.h>
5#include <test/jtx/multisign.h>
6#include <test/jtx/xchain_bridge.h>
7
8#include <xrpl/beast/unit_test.h>
9#include <xrpl/json/json_value.h>
10#include <xrpl/protocol/AccountID.h>
11#include <xrpl/protocol/ErrorCodes.h>
12#include <xrpl/protocol/STXChainBridge.h>
13#include <xrpl/protocol/jss.h>
14
15#if (defined(__clang_major__) && __clang_major__ < 15)
16#include <experimental/source_location>
18#else
19#include <source_location>
21#endif
22namespace ripple {
23
24namespace test {
25
39
41 {jss::account, FieldType::AccountField},
42 {jss::accounts, FieldType::TwoAccountArrayField},
43 {jss::authorize, FieldType::AccountField},
44 {jss::authorized, FieldType::AccountField},
45 {jss::credential_type, FieldType::BlobField},
46 {jss::currency, FieldType::CurrencyField},
47 {jss::issuer, FieldType::AccountField},
48 {jss::oracle_document_id, FieldType::UInt32Field},
49 {jss::owner, FieldType::AccountField},
50 {jss::seq, FieldType::UInt32Field},
51 {jss::subject, FieldType::AccountField},
52 {jss::ticket_seq, FieldType::UInt32Field},
53};
54
57{
58 auto it = std::ranges::find_if(mappings, [&fieldName](auto const& pair) {
59 return pair.first == fieldName;
60 });
61 if (it != mappings.end())
62 {
63 return it->second;
64 }
65 else
66 {
67 Throw<std::runtime_error>(
68 "`mappings` is missing field " + std::string(fieldName.c_str()));
69 }
70}
71
74{
75 switch (typeID)
76 {
78 return "number";
80 return "number";
82 return "hex string";
84 return "AccountID";
86 return "hex string";
88 return "Currency";
90 return "array";
92 return "hex string or object";
94 return "length-2 array of Accounts";
95 default:
96 Throw<std::runtime_error>(
97 "unknown type " + std::to_string(static_cast<uint8_t>(typeID)));
98 }
99}
100
102{
103 void
105 Json::Value const& jv,
106 std::string const& err,
107 std::string const& msg,
108 source_location const location = source_location::current())
109 {
110 if (BEAST_EXPECT(jv.isMember(jss::status)))
111 BEAST_EXPECTS(
112 jv[jss::status] == "error", std::to_string(location.line()));
113 if (BEAST_EXPECT(jv.isMember(jss::error)))
114 BEAST_EXPECTS(
115 jv[jss::error] == err,
116 "Expected error " + err + ", received " +
117 jv[jss::error].asString() + ", at line " +
118 std::to_string(location.line()) + ", " +
119 jv.toStyledString());
120 if (msg.empty())
121 {
122 BEAST_EXPECTS(
123 jv[jss::error_message] == Json::nullValue ||
124 jv[jss::error_message] == "",
125 "Expected no error message, received \"" +
126 jv[jss::error_message].asString() + "\", at line " +
127 std::to_string(location.line()) + ", " +
128 jv.toStyledString());
129 }
130 else if (BEAST_EXPECT(jv.isMember(jss::error_message)))
131 BEAST_EXPECTS(
132 jv[jss::error_message] == msg,
133 "Expected error message \"" + msg + "\", received \"" +
134 jv[jss::error_message].asString() + "\", at line " +
135 std::to_string(location.line()) + ", " +
136 jv.toStyledString());
137 }
138
141 {
142 static Json::Value const injectObject = []() {
144 obj[jss::account] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
145 obj[jss::ledger_index] = "validated";
146 return obj;
147 }();
148 static Json::Value const injectArray = []() {
150 arr[0u] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
151 arr[1u] = "validated";
152 return arr;
153 }();
154 static std::array<Json::Value, 21> const allBadValues = {
155 "", // 0
156 true, // 1
157 1, // 2
158 "1", // 3
159 -1, // 4
160 1.1, // 5
161 "-1", // 6
162 "abcdef", // 7
163 "ABCDEF", // 8
164 "12KK", // 9
165 "0123456789ABCDEFGH", // 10
166 "rJxKV9e9p6wiPw!!!!xrJ4X1n98LosPL1sgcJW", // 11
167 "rPSTrR5yEr11uMkfsz1kHCp9jK4aoa3Avv", // 12
168 "n9K2isxwTxcSHJKxMkJznDoWXAUs7NNy49H9Fknz1pC7oHAH3kH9", // 13
169 "USD", // 14
170 "USDollars", // 15
171 "5233D68B4D44388F98559DE42903767803EFA7C1F8D01413FC16EE6B01403D"
172 "6D", // 16
173 Json::arrayValue, // 17
174 Json::objectValue, // 18
175 injectObject, // 19
176 injectArray // 20
177 };
178
179 auto remove =
182 indices.begin(), indices.end());
184 values.reserve(allBadValues.size() - indexSet.size());
185 for (std::size_t i = 0; i < allBadValues.size(); ++i)
186 {
187 if (indexSet.find(i) == indexSet.end())
188 {
189 values.push_back(allBadValues[i]);
190 }
191 }
192 return values;
193 };
194
195 static auto const& badUInt32Values = remove({2, 3});
196 static auto const& badUInt64Values = remove({2, 3});
197 static auto const& badHashValues = remove({2, 3, 7, 8, 16});
198 static auto const& badAccountValues = remove({12});
199 static auto const& badBlobValues = remove({3, 7, 8, 16});
200 static auto const& badCurrencyValues = remove({14});
201 static auto const& badArrayValues = remove({17, 20});
202 static auto const& badIndexValues = remove({12, 16, 18, 19});
203
204 switch (fieldType)
205 {
207 return badUInt32Values;
209 return badUInt64Values;
211 return badHashValues;
213 return badAccountValues;
215 return badBlobValues;
217 return badCurrencyValues;
220 return badArrayValues;
222 return badIndexValues;
223 default:
224 Throw<std::runtime_error>(
225 "unknown type " +
226 std::to_string(static_cast<uint8_t>(fieldType)));
227 }
228 }
229
232 {
233 static Json::Value const twoAccountArray = []() {
235 arr[0u] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
236 arr[1u] = "r4MrUGTdB57duTnRs6KbsRGQXgkseGb1b5";
237 return arr;
238 }();
239
240 auto const typeID = getFieldType(fieldName);
241 switch (typeID)
242 {
244 return 1;
246 return 1;
248 return "5233D68B4D44388F98559DE42903767803EFA7C1F8D01413FC16EE6"
249 "B01403D6D";
251 return "r4MrUGTdB57duTnRs6KbsRGQXgkseGb1b5";
253 return "ABCDEF";
255 return "USD";
257 return Json::arrayValue;
259 return "5233D68B4D44388F98559DE42903767803EFA7C1F8D01413FC16EE6"
260 "B01403D6D";
262 return twoAccountArray;
263 default:
264 Throw<std::runtime_error>(
265 "unknown type " +
266 std::to_string(static_cast<uint8_t>(typeID)));
267 }
268 }
269
270 void
272 test::jtx::Env& env,
273 Json::Value correctRequest,
274 Json::StaticString const fieldName,
275 FieldType const typeID,
276 std::string const& expectedError,
277 bool required = true,
278 source_location const location = source_location::current())
279 {
280 forAllApiVersions([&, this](unsigned apiVersion) {
281 if (required)
282 {
283 correctRequest.removeMember(fieldName);
284 Json::Value const jrr = env.rpc(
285 apiVersion,
286 "json",
287 "ledger_entry",
288 to_string(correctRequest))[jss::result];
289 if (apiVersion < 2u)
290 checkErrorValue(jrr, "unknownOption", "", location);
291 else
293 jrr,
294 "invalidParams",
295 "No ledger_entry params provided.",
296 location);
297 }
298 auto tryField = [&](Json::Value fieldValue) -> void {
299 correctRequest[fieldName] = fieldValue;
300 Json::Value const jrr = env.rpc(
301 apiVersion,
302 "json",
303 "ledger_entry",
304 to_string(correctRequest))[jss::result];
305 auto const expectedErrMsg =
306 RPC::expected_field_message(fieldName, getTypeName(typeID));
307 checkErrorValue(jrr, expectedError, expectedErrMsg, location);
308 };
309
310 auto const& badValues = getBadValues(typeID);
311 for (auto const& value : badValues)
312 {
313 tryField(value);
314 }
315 if (required)
316 {
317 tryField(Json::nullValue);
318 }
319 });
320 }
321
322 void
324 test::jtx::Env& env,
325 Json::Value correctRequest,
326 Json::StaticString parentFieldName,
327 Json::StaticString fieldName,
328 FieldType typeID,
329 std::string const& expectedError,
330 bool required = true,
331 source_location const location = source_location::current())
332 {
333 forAllApiVersions([&, this](unsigned apiVersion) {
334 if (required)
335 {
336 correctRequest[parentFieldName].removeMember(fieldName);
337 Json::Value const jrr = env.rpc(
338 apiVersion,
339 "json",
340 "ledger_entry",
341 to_string(correctRequest))[jss::result];
343 jrr,
344 "malformedRequest",
346 location);
347
348 correctRequest[parentFieldName][fieldName] = Json::nullValue;
349 Json::Value const jrr2 = env.rpc(
350 apiVersion,
351 "json",
352 "ledger_entry",
353 to_string(correctRequest))[jss::result];
355 jrr2,
356 "malformedRequest",
358 location);
359 }
360 auto tryField = [&](Json::Value fieldValue) -> void {
361 correctRequest[parentFieldName][fieldName] = fieldValue;
362
363 Json::Value const jrr = env.rpc(
364 apiVersion,
365 "json",
366 "ledger_entry",
367 to_string(correctRequest))[jss::result];
369 jrr,
370 expectedError,
371 RPC::expected_field_message(fieldName, getTypeName(typeID)),
372 location);
373 };
374
375 auto const& badValues = getBadValues(typeID);
376 for (auto const& value : badValues)
377 {
378 tryField(value);
379 }
380 });
381 }
382
383 // No subfields
384 void
386 test::jtx::Env& env,
387 Json::StaticString const& parentField,
388 source_location const location = source_location::current())
389 {
391 env,
392 Json::Value{},
393 parentField,
395 "malformedRequest",
396 true,
397 location);
398 }
399
406
407 void
409 test::jtx::Env& env,
410 Json::StaticString const& parentField,
411 std::vector<Subfield> const& subfields,
412 source_location const location = source_location::current())
413 {
415 env,
416 Json::Value{},
417 parentField,
419 "malformedRequest",
420 true,
421 location);
422
423 Json::Value correctOutput;
424 correctOutput[parentField] = Json::objectValue;
425 for (auto const& subfield : subfields)
426 {
427 correctOutput[parentField][subfield.fieldName] =
428 getCorrectValue(subfield.fieldName);
429 }
430
431 for (auto const& subfield : subfields)
432 {
433 auto const fieldType = getFieldType(subfield.fieldName);
435 env,
436 correctOutput,
437 parentField,
438 subfield.fieldName,
439 fieldType,
440 subfield.malformedErrorMsg,
441 subfield.required,
442 location);
443 }
444 }
445
446 void
448 {
449 testcase("Invalid requests");
450 using namespace test::jtx;
451 Env env{*this};
452 Account const alice{"alice"};
453 env.fund(XRP(10000), alice);
454 env.close();
455 {
456 // Missing ledger_entry ledger_hash
457 Json::Value jvParams;
458 jvParams[jss::account_root] = alice.human();
459 jvParams[jss::ledger_hash] =
460 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
461 "AA";
462 auto const jrr = env.rpc(
463 "json", "ledger_entry", to_string(jvParams))[jss::result];
464 checkErrorValue(jrr, "lgrNotFound", "ledgerNotFound");
465 }
466 {
467 // Missing ledger_entry ledger_hash
468 Json::Value jvParams;
469 jvParams[jss::account_root] = alice.human();
470 auto const typeId = FieldType::HashField;
471
472 forAllApiVersions([&, this](unsigned apiVersion) {
473 auto tryField = [&](Json::Value fieldValue) -> void {
474 jvParams[jss::ledger_hash] = fieldValue;
475 Json::Value const jrr = env.rpc(
476 apiVersion,
477 "json",
478 "ledger_entry",
479 to_string(jvParams))[jss::result];
480 auto const expectedErrMsg = fieldValue.isString()
481 ? "ledgerHashMalformed"
482 : "ledgerHashNotString";
483 checkErrorValue(jrr, "invalidParams", expectedErrMsg);
484 };
485
486 auto const& badValues = getBadValues(typeId);
487 for (auto const& value : badValues)
488 {
489 tryField(value);
490 }
491 });
492 }
493
494 {
495 // ask for an zero index
496 Json::Value jvParams;
497 jvParams[jss::ledger_index] = "validated";
498 jvParams[jss::index] =
499 "00000000000000000000000000000000000000000000000000000000000000"
500 "00";
501 auto const jrr = env.rpc(
502 "json", "ledger_entry", to_string(jvParams))[jss::result];
503 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
504 }
505
506 forAllApiVersions([&, this](unsigned apiVersion) {
507 // "features" is not an option supported by ledger_entry.
508 {
510 jvParams[jss::features] =
511 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
512 "AAAAAAAAAA";
513 jvParams[jss::api_version] = apiVersion;
514 Json::Value const jrr = env.rpc(
515 "json", "ledger_entry", to_string(jvParams))[jss::result];
516
517 if (apiVersion < 2u)
518 checkErrorValue(jrr, "unknownOption", "");
519 else
521 jrr,
522 "invalidParams",
523 "No ledger_entry params provided.");
524 }
525 });
526 }
527
528 void
530 {
531 testcase("AccountRoot");
532 using namespace test::jtx;
533
534 auto cfg = envconfig();
535 cfg->FEES.reference_fee = 10;
536 Env env{*this, std::move(cfg)};
537
538 Account const alice{"alice"};
539 env.fund(XRP(10000), alice);
540 env.close();
541
542 std::string const ledgerHash{to_string(env.closed()->info().hash)};
543 {
544 // Exercise ledger_closed along the way.
545 Json::Value const jrr = env.rpc("ledger_closed")[jss::result];
546 BEAST_EXPECT(jrr[jss::ledger_hash] == ledgerHash);
547 BEAST_EXPECT(jrr[jss::ledger_index] == 3);
548 }
549
550 std::string accountRootIndex;
551 {
552 // Request alice's account root.
553 Json::Value jvParams;
554 jvParams[jss::account_root] = alice.human();
555 jvParams[jss::ledger_hash] = ledgerHash;
556 Json::Value const jrr = env.rpc(
557 "json", "ledger_entry", to_string(jvParams))[jss::result];
558 BEAST_EXPECT(jrr.isMember(jss::node));
559 BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
560 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
561 accountRootIndex = jrr[jss::index].asString();
562 }
563 {
564 constexpr char alicesAcctRootBinary[]{
565 "1100612200800000240000000425000000032D00000000559CE54C3B934E4"
566 "73A995B477E92EC229F99CED5B62BF4D2ACE4DC42719103AE2F6240000002"
567 "540BE4008114AE123A8556F3CF91154711376AFB0F894F832B3D"};
568
569 // Request alice's account root, but with binary == true;
570 Json::Value jvParams;
571 jvParams[jss::account_root] = alice.human();
572 jvParams[jss::binary] = 1;
573 jvParams[jss::ledger_hash] = ledgerHash;
574 Json::Value const jrr = env.rpc(
575 "json", "ledger_entry", to_string(jvParams))[jss::result];
576 BEAST_EXPECT(jrr.isMember(jss::node_binary));
577 BEAST_EXPECT(jrr[jss::node_binary] == alicesAcctRootBinary);
578 }
579 {
580 // Request alice's account root using the index.
581 Json::Value jvParams;
582 jvParams[jss::index] = accountRootIndex;
583 Json::Value const jrr = env.rpc(
584 "json", "ledger_entry", to_string(jvParams))[jss::result];
585 BEAST_EXPECT(!jrr.isMember(jss::node_binary));
586 BEAST_EXPECT(jrr.isMember(jss::node));
587 BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
588 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
589 }
590 {
591 // Request alice's account root by index, but with binary == false.
592 Json::Value jvParams;
593 jvParams[jss::index] = accountRootIndex;
594 jvParams[jss::binary] = 0;
595 Json::Value const jrr = env.rpc(
596 "json", "ledger_entry", to_string(jvParams))[jss::result];
597 BEAST_EXPECT(jrr.isMember(jss::node));
598 BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
599 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
600 }
601 {
602 // Check alias
603 Json::Value jvParams;
604 jvParams[jss::account] = alice.human();
605 jvParams[jss::ledger_hash] = ledgerHash;
606 Json::Value const jrr = env.rpc(
607 "json", "ledger_entry", to_string(jvParams))[jss::result];
608 BEAST_EXPECT(jrr.isMember(jss::node));
609 BEAST_EXPECT(jrr[jss::node][jss::Account] == alice.human());
610 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "10000000000");
611 accountRootIndex = jrr[jss::index].asString();
612 }
613 {
614 // Check malformed cases
615 Json::Value jvParams;
617 env,
618 jvParams,
619 jss::account_root,
621 "malformedAddress");
622 }
623 {
624 // Request an account that is not in the ledger.
625 Json::Value jvParams;
626 jvParams[jss::account_root] = Account("bob").human();
627 jvParams[jss::ledger_hash] = ledgerHash;
628 Json::Value const jrr = env.rpc(
629 "json", "ledger_entry", to_string(jvParams))[jss::result];
630 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
631 }
632 }
633
634 void
636 {
637 testcase("Check");
638 using namespace test::jtx;
639 Env env{*this};
640 Account const alice{"alice"};
641 env.fund(XRP(10000), alice);
642 env.close();
643
644 auto const checkId = keylet::check(env.master, env.seq(env.master));
645
646 env(check::create(env.master, alice, XRP(100)));
647 env.close();
648
649 std::string const ledgerHash{to_string(env.closed()->info().hash)};
650 {
651 // Request a check.
652 Json::Value jvParams;
653 jvParams[jss::check] = to_string(checkId.key);
654 jvParams[jss::ledger_hash] = ledgerHash;
655 Json::Value const jrr = env.rpc(
656 "json", "ledger_entry", to_string(jvParams))[jss::result];
657 BEAST_EXPECT(
658 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Check);
659 BEAST_EXPECT(jrr[jss::node][sfSendMax.jsonName] == "100000000");
660 }
661 {
662 // Request an index that is not a check. We'll use alice's
663 // account root index.
664 std::string accountRootIndex;
665 {
666 Json::Value jvParams;
667 jvParams[jss::account_root] = alice.human();
668 Json::Value const jrr = env.rpc(
669 "json", "ledger_entry", to_string(jvParams))[jss::result];
670 accountRootIndex = jrr[jss::index].asString();
671 }
672 Json::Value jvParams;
673 jvParams[jss::check] = accountRootIndex;
674 jvParams[jss::ledger_hash] = ledgerHash;
675 Json::Value const jrr = env.rpc(
676 "json", "ledger_entry", to_string(jvParams))[jss::result];
678 jrr, "unexpectedLedgerType", "Unexpected ledger type.");
679 }
680 {
681 // Check malformed cases
682 runLedgerEntryTest(env, jss::check);
683 }
684 }
685
686 void
688 {
689 testcase("Credentials");
690
691 using namespace test::jtx;
692
693 Env env(*this);
694 Account const issuer{"issuer"};
695 Account const alice{"alice"};
696 Account const bob{"bob"};
697 char const credType[] = "abcde";
698
699 env.fund(XRP(5000), issuer, alice, bob);
700 env.close();
701
702 // Setup credentials with DepositAuth object for Alice and Bob
703 env(credentials::create(alice, issuer, credType));
704 env.close();
705
706 {
707 // Succeed
708 auto jv = credentials::ledgerEntry(env, alice, issuer, credType);
709 BEAST_EXPECT(
710 jv.isObject() && jv.isMember(jss::result) &&
711 !jv[jss::result].isMember(jss::error) &&
712 jv[jss::result].isMember(jss::node) &&
713 jv[jss::result][jss::node].isMember(
714 sfLedgerEntryType.jsonName) &&
715 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
716 jss::Credential);
717
718 std::string const credIdx = jv[jss::result][jss::index].asString();
719
720 jv = credentials::ledgerEntry(env, credIdx);
721 BEAST_EXPECT(
722 jv.isObject() && jv.isMember(jss::result) &&
723 !jv[jss::result].isMember(jss::error) &&
724 jv[jss::result].isMember(jss::node) &&
725 jv[jss::result][jss::node].isMember(
726 sfLedgerEntryType.jsonName) &&
727 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
728 jss::Credential);
729 }
730
731 {
732 // Fail, credential doesn't exist
733 auto const jv = credentials::ledgerEntry(
734 env,
735 "48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
736 "E4");
738 jv[jss::result], "entryNotFound", "Entry not found.");
739 }
740
741 {
742 // Check all malformed cases
744 env,
745 jss::credential,
746 {
747 {jss::subject, "malformedRequest"},
748 {jss::issuer, "malformedRequest"},
749 {jss::credential_type, "malformedRequest"},
750 });
751 }
752 }
753
754 void
756 {
757 testcase("Delegate");
758
759 using namespace test::jtx;
760
761 Env env{*this};
762 Account const alice{"alice"};
763 Account const bob{"bob"};
764 env.fund(XRP(10000), alice, bob);
765 env.close();
766 env(delegate::set(alice, bob, {"Payment", "CheckCreate"}));
767 env.close();
768 std::string const ledgerHash{to_string(env.closed()->info().hash)};
769 std::string delegateIndex;
770 {
771 // Request by account and authorize
772 Json::Value jvParams;
773 jvParams[jss::delegate][jss::account] = alice.human();
774 jvParams[jss::delegate][jss::authorize] = bob.human();
775 jvParams[jss::ledger_hash] = ledgerHash;
776 Json::Value const jrr = env.rpc(
777 "json", "ledger_entry", to_string(jvParams))[jss::result];
778 BEAST_EXPECT(
779 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Delegate);
780 BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
781 BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == bob.human());
782 delegateIndex = jrr[jss::node][jss::index].asString();
783 }
784 {
785 // Request by index.
786 Json::Value jvParams;
787 jvParams[jss::delegate] = delegateIndex;
788 jvParams[jss::ledger_hash] = ledgerHash;
789 Json::Value const jrr = env.rpc(
790 "json", "ledger_entry", to_string(jvParams))[jss::result];
791 BEAST_EXPECT(
792 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Delegate);
793 BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
794 BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == bob.human());
795 }
796
797 {
798 // Check all malformed cases
800 env,
801 jss::delegate,
802 {
803 {jss::account, "malformedAddress"},
804 {jss::authorize, "malformedAddress"},
805 });
806 }
807 }
808
809 void
811 {
812 testcase("Deposit Preauth");
813
814 using namespace test::jtx;
815
816 Env env{*this};
817 Account const alice{"alice"};
818 Account const becky{"becky"};
819
820 env.fund(XRP(10000), alice, becky);
821 env.close();
822
823 env(deposit::auth(alice, becky));
824 env.close();
825
826 std::string const ledgerHash{to_string(env.closed()->info().hash)};
827 std::string depositPreauthIndex;
828 {
829 // Request a depositPreauth by owner and authorized.
830 Json::Value jvParams;
831 jvParams[jss::deposit_preauth][jss::owner] = alice.human();
832 jvParams[jss::deposit_preauth][jss::authorized] = becky.human();
833 jvParams[jss::ledger_hash] = ledgerHash;
834 Json::Value const jrr = env.rpc(
835 "json", "ledger_entry", to_string(jvParams))[jss::result];
836
837 BEAST_EXPECT(
838 jrr[jss::node][sfLedgerEntryType.jsonName] ==
839 jss::DepositPreauth);
840 BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
841 BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == becky.human());
842 depositPreauthIndex = jrr[jss::node][jss::index].asString();
843 }
844 {
845 // Request a depositPreauth by index.
846 Json::Value jvParams;
847 jvParams[jss::deposit_preauth] = depositPreauthIndex;
848 jvParams[jss::ledger_hash] = ledgerHash;
849 Json::Value const jrr = env.rpc(
850 "json", "ledger_entry", to_string(jvParams))[jss::result];
851
852 BEAST_EXPECT(
853 jrr[jss::node][sfLedgerEntryType.jsonName] ==
854 jss::DepositPreauth);
855 BEAST_EXPECT(jrr[jss::node][sfAccount.jsonName] == alice.human());
856 BEAST_EXPECT(jrr[jss::node][sfAuthorize.jsonName] == becky.human());
857 }
858 {
859 // test all missing/malformed field cases
861 env,
862 jss::deposit_preauth,
863 {
864 {jss::owner, "malformedOwner"},
865 {jss::authorized, "malformedAuthorized", false},
866 });
867 }
868 }
869
870 void
872 {
873 testcase("Deposit Preauth with credentials");
874
875 using namespace test::jtx;
876
877 Env env(*this);
878 Account const issuer{"issuer"};
879 Account const alice{"alice"};
880 Account const bob{"bob"};
881 char const credType[] = "abcde";
882
883 env.fund(XRP(5000), issuer, alice, bob);
884 env.close();
885
886 {
887 // Setup Bob with DepositAuth
888 env(fset(bob, asfDepositAuth));
889 env.close();
890 env(deposit::authCredentials(bob, {{issuer, credType}}));
891 env.close();
892 }
893
894 {
895 // Succeed
896 Json::Value jvParams;
897 jvParams[jss::ledger_index] = jss::validated;
898 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
899
900 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
902 auto& arr(
903 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
904
905 Json::Value jo;
906 jo[jss::issuer] = issuer.human();
907 jo[jss::credential_type] = strHex(std::string_view(credType));
908 arr.append(std::move(jo));
909 auto const jrr =
910 env.rpc("json", "ledger_entry", to_string(jvParams));
911
912 BEAST_EXPECT(
913 jrr.isObject() && jrr.isMember(jss::result) &&
914 !jrr[jss::result].isMember(jss::error) &&
915 jrr[jss::result].isMember(jss::node) &&
916 jrr[jss::result][jss::node].isMember(
917 sfLedgerEntryType.jsonName) &&
918 jrr[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
919 jss::DepositPreauth);
920 }
921
922 {
923 // Failed, invalid account
924 Json::Value jvParams;
925 jvParams[jss::ledger_index] = jss::validated;
926 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
927
928 auto tryField = [&](Json::Value fieldValue) -> void {
930 Json::Value jo;
931 jo[jss::issuer] = fieldValue;
932 jo[jss::credential_type] = strHex(std::string_view(credType));
933 arr.append(jo);
934 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
935 arr;
936
937 Json::Value const jrr = env.rpc(
938 "json", "ledger_entry", to_string(jvParams))[jss::result];
939 auto const expectedErrMsg = fieldValue.isNull()
940 ? RPC::missing_field_message(jss::issuer.c_str())
941 : RPC::expected_field_message(jss::issuer, "AccountID");
943 jrr, "malformedAuthorizedCredentials", expectedErrMsg);
944 };
945
946 auto const& badValues = getBadValues(FieldType::AccountField);
947 for (auto const& value : badValues)
948 {
949 tryField(value);
950 }
951 tryField(Json::nullValue);
952 }
953
954 {
955 // Failed, duplicates in credentials
956 Json::Value jvParams;
957 jvParams[jss::ledger_index] = jss::validated;
958 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
959
960 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
962 auto& arr(
963 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
964
965 Json::Value jo;
966 jo[jss::issuer] = issuer.human();
967 jo[jss::credential_type] = strHex(std::string_view(credType));
968 arr.append(jo);
969 arr.append(std::move(jo));
970 auto const jrr =
971 env.rpc("json", "ledger_entry", to_string(jvParams));
973 jrr[jss::result],
974 "malformedAuthorizedCredentials",
976 jss::authorized_credentials, "array"));
977 }
978
979 {
980 // Failed, invalid credential_type
981 Json::Value jvParams;
982 jvParams[jss::ledger_index] = jss::validated;
983 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
984
985 auto tryField = [&](Json::Value fieldValue) -> void {
987 Json::Value jo;
988 jo[jss::issuer] = issuer.human();
989 jo[jss::credential_type] = fieldValue;
990 arr.append(jo);
991 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
992 arr;
993
994 Json::Value const jrr = env.rpc(
995 "json", "ledger_entry", to_string(jvParams))[jss::result];
996 auto const expectedErrMsg = fieldValue.isNull()
997 ? RPC::missing_field_message(jss::credential_type.c_str())
999 jss::credential_type, "hex string");
1001 jrr, "malformedAuthorizedCredentials", expectedErrMsg);
1002 };
1003
1004 auto const& badValues = getBadValues(FieldType::BlobField);
1005 for (auto const& value : badValues)
1006 {
1007 tryField(value);
1008 }
1009 tryField(Json::nullValue);
1010 }
1011
1012 {
1013 // Failed, authorized and authorized_credentials both present
1014 Json::Value jvParams;
1015 jvParams[jss::ledger_index] = jss::validated;
1016 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1017 jvParams[jss::deposit_preauth][jss::authorized] = alice.human();
1018
1019 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
1021 auto& arr(
1022 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
1023
1024 Json::Value jo;
1025 jo[jss::issuer] = issuer.human();
1026 jo[jss::credential_type] = strHex(std::string_view(credType));
1027 arr.append(std::move(jo));
1028
1029 auto const jrr =
1030 env.rpc("json", "ledger_entry", to_string(jvParams));
1032 jrr[jss::result],
1033 "malformedRequest",
1034 "Must have exactly one of `authorized` and "
1035 "`authorized_credentials`.");
1036 }
1037
1038 {
1039 // Failed, authorized_credentials is not an array
1040 Json::Value jvParams;
1041 jvParams[jss::ledger_index] = jss::validated;
1042 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1044 env,
1045 jvParams,
1046 jss::deposit_preauth,
1047 jss::authorized_credentials,
1049 "malformedAuthorizedCredentials",
1050 false);
1051 }
1052
1053 {
1054 // Failed, authorized_credentials contains string data
1055 Json::Value jvParams;
1056 jvParams[jss::ledger_index] = jss::validated;
1057 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1058 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
1060 auto& arr(
1061 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
1062 arr.append("foobar");
1063
1064 auto const jrr =
1065 env.rpc("json", "ledger_entry", to_string(jvParams));
1067 jrr[jss::result],
1068 "malformedAuthorizedCredentials",
1069 "Invalid field 'authorized_credentials', not array.");
1070 }
1071
1072 {
1073 // Failed, authorized_credentials contains arrays
1074 Json::Value jvParams;
1075 jvParams[jss::ledger_index] = jss::validated;
1076 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1077 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
1079 auto& arr(
1080 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
1081 Json::Value payload = Json::arrayValue;
1082 payload.append(42);
1083 arr.append(std::move(payload));
1084
1085 auto const jrr =
1086 env.rpc("json", "ledger_entry", to_string(jvParams));
1088 jrr[jss::result],
1089 "malformedAuthorizedCredentials",
1090 "Invalid field 'authorized_credentials', not array.");
1091 }
1092
1093 {
1094 // Failed, authorized_credentials is empty array
1095 Json::Value jvParams;
1096 jvParams[jss::ledger_index] = jss::validated;
1097 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1098 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
1100
1101 auto const jrr =
1102 env.rpc("json", "ledger_entry", to_string(jvParams));
1104 jrr[jss::result],
1105 "malformedAuthorizedCredentials",
1106 "Invalid field 'authorized_credentials', not array.");
1107 }
1108
1109 {
1110 // Failed, authorized_credentials is too long
1111 static std::array<std::string_view, 9> const credTypes = {
1112 "cred1",
1113 "cred2",
1114 "cred3",
1115 "cred4",
1116 "cred5",
1117 "cred6",
1118 "cred7",
1119 "cred8",
1120 "cred9"};
1121 static_assert(
1122 sizeof(credTypes) / sizeof(credTypes[0]) >
1124
1125 Json::Value jvParams;
1126 jvParams[jss::ledger_index] = jss::validated;
1127 jvParams[jss::deposit_preauth][jss::owner] = bob.human();
1128 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
1130
1131 auto& arr(
1132 jvParams[jss::deposit_preauth][jss::authorized_credentials]);
1133
1134 for (auto cred : credTypes)
1135 {
1136 Json::Value jo;
1137 jo[jss::issuer] = issuer.human();
1138 jo[jss::credential_type] = strHex(std::string_view(cred));
1139 arr.append(std::move(jo));
1140 }
1141
1142 auto const jrr =
1143 env.rpc("json", "ledger_entry", to_string(jvParams));
1145 jrr[jss::result],
1146 "malformedAuthorizedCredentials",
1147 "Invalid field 'authorized_credentials', not array.");
1148 }
1149 }
1150
1151 void
1153 {
1154 testcase("Directory");
1155 using namespace test::jtx;
1156 Env env{*this};
1157 Account const alice{"alice"};
1158 Account const gw{"gateway"};
1159 auto const USD = gw["USD"];
1160 env.fund(XRP(10000), alice, gw);
1161 env.close();
1162
1163 env.trust(USD(1000), alice);
1164 env.close();
1165
1166 // Run up the number of directory entries so alice has two
1167 // directory nodes.
1168 for (int d = 1'000'032; d >= 1'000'000; --d)
1169 {
1170 env(offer(alice, USD(1), drops(d)));
1171 }
1172 env.close();
1173
1174 std::string const ledgerHash{to_string(env.closed()->info().hash)};
1175 {
1176 // Exercise ledger_closed along the way.
1177 Json::Value const jrr = env.rpc("ledger_closed")[jss::result];
1178 BEAST_EXPECT(jrr[jss::ledger_hash] == ledgerHash);
1179 BEAST_EXPECT(jrr[jss::ledger_index] == 5);
1180 }
1181
1182 std::string const dirRootIndex =
1183 "A33EC6BB85FB5674074C4A3A43373BB17645308F3EAE1933E3E35252162B217D";
1184 {
1185 // Locate directory by index.
1186 Json::Value jvParams;
1187 jvParams[jss::directory] = dirRootIndex;
1188 jvParams[jss::ledger_hash] = ledgerHash;
1189 Json::Value const jrr = env.rpc(
1190 "json", "ledger_entry", to_string(jvParams))[jss::result];
1191 BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 32);
1192 }
1193 {
1194 // Locate directory by directory root.
1195 Json::Value jvParams;
1196 jvParams[jss::directory] = Json::objectValue;
1197 jvParams[jss::directory][jss::dir_root] = dirRootIndex;
1198 Json::Value const jrr = env.rpc(
1199 "json", "ledger_entry", to_string(jvParams))[jss::result];
1200 BEAST_EXPECT(jrr[jss::index] == dirRootIndex);
1201 }
1202 {
1203 // Locate directory by owner.
1204 Json::Value jvParams;
1205 jvParams[jss::directory] = Json::objectValue;
1206 jvParams[jss::directory][jss::owner] = alice.human();
1207 jvParams[jss::ledger_hash] = ledgerHash;
1208 Json::Value const jrr = env.rpc(
1209 "json", "ledger_entry", to_string(jvParams))[jss::result];
1210 BEAST_EXPECT(jrr[jss::index] == dirRootIndex);
1211 }
1212 {
1213 // Locate directory by directory root and sub_index.
1214 Json::Value jvParams;
1215 jvParams[jss::directory] = Json::objectValue;
1216 jvParams[jss::directory][jss::dir_root] = dirRootIndex;
1217 jvParams[jss::directory][jss::sub_index] = 1;
1218 Json::Value const jrr = env.rpc(
1219 "json", "ledger_entry", to_string(jvParams))[jss::result];
1220 BEAST_EXPECT(jrr[jss::index] != dirRootIndex);
1221 BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 2);
1222 }
1223 {
1224 // Locate directory by owner and sub_index.
1225 Json::Value jvParams;
1226 jvParams[jss::directory] = Json::objectValue;
1227 jvParams[jss::directory][jss::owner] = alice.human();
1228 jvParams[jss::directory][jss::sub_index] = 1;
1229 jvParams[jss::ledger_hash] = ledgerHash;
1230 Json::Value const jrr = env.rpc(
1231 "json", "ledger_entry", to_string(jvParams))[jss::result];
1232 BEAST_EXPECT(jrr[jss::index] != dirRootIndex);
1233 BEAST_EXPECT(jrr[jss::node][sfIndexes.jsonName].size() == 2);
1234 }
1235 {
1236 // Bad directory argument.
1237 Json::Value jvParams;
1238 jvParams[jss::ledger_hash] = ledgerHash;
1240 env,
1241 jvParams,
1242 jss::directory,
1244 "malformedRequest");
1245 }
1246 {
1247 // Non-integer sub_index.
1248 Json::Value jvParams;
1249 jvParams[jss::directory] = Json::objectValue;
1250 jvParams[jss::directory][jss::dir_root] = dirRootIndex;
1251 jvParams[jss::ledger_hash] = ledgerHash;
1253 env,
1254 jvParams,
1255 jss::directory,
1256 jss::sub_index,
1258 "malformedRequest",
1259 false);
1260 }
1261 {
1262 // Malformed owner entry.
1263 Json::Value jvParams;
1264 jvParams[jss::directory] = Json::objectValue;
1265
1266 jvParams[jss::ledger_hash] = ledgerHash;
1268 env,
1269 jvParams,
1270 jss::directory,
1271 jss::owner,
1273 "malformedAddress",
1274 false);
1275 }
1276 {
1277 // Malformed directory object. Specifies both dir_root and owner.
1278 Json::Value jvParams;
1279 jvParams[jss::directory] = Json::objectValue;
1280 jvParams[jss::directory][jss::owner] = alice.human();
1281 jvParams[jss::directory][jss::dir_root] = dirRootIndex;
1282 jvParams[jss::ledger_hash] = ledgerHash;
1283 Json::Value const jrr = env.rpc(
1284 "json", "ledger_entry", to_string(jvParams))[jss::result];
1286 jrr,
1287 "malformedRequest",
1288 "Must have exactly one of `owner` and `dir_root` fields.");
1289 }
1290 {
1291 // Incomplete directory object. Missing both dir_root and owner.
1292 Json::Value jvParams;
1293 jvParams[jss::directory] = Json::objectValue;
1294 jvParams[jss::directory][jss::sub_index] = 1;
1295 jvParams[jss::ledger_hash] = ledgerHash;
1296 Json::Value const jrr = env.rpc(
1297 "json", "ledger_entry", to_string(jvParams))[jss::result];
1299 jrr,
1300 "malformedRequest",
1301 "Must have exactly one of `owner` and `dir_root` fields.");
1302 }
1303 }
1304
1305 void
1307 {
1308 testcase("Escrow");
1309 using namespace test::jtx;
1310 Env env{*this};
1311 Account const alice{"alice"};
1312 env.fund(XRP(10000), alice);
1313 env.close();
1314
1315 // Lambda to create an escrow.
1316 auto escrowCreate = [](test::jtx::Account const& account,
1317 test::jtx::Account const& to,
1318 STAmount const& amount,
1319 NetClock::time_point const& cancelAfter) {
1320 Json::Value jv;
1321 jv[jss::TransactionType] = jss::EscrowCreate;
1322 jv[jss::Account] = account.human();
1323 jv[jss::Destination] = to.human();
1324 jv[jss::Amount] = amount.getJson(JsonOptions::none);
1325 jv[sfFinishAfter.jsonName] =
1326 cancelAfter.time_since_epoch().count() + 2;
1327 return jv;
1328 };
1329
1330 using namespace std::chrono_literals;
1331 env(escrowCreate(alice, alice, XRP(333), env.now() + 2s));
1332 env.close();
1333
1334 std::string const ledgerHash{to_string(env.closed()->info().hash)};
1335 std::string escrowIndex;
1336 {
1337 // Request the escrow using owner and sequence.
1338 Json::Value jvParams;
1339 jvParams[jss::escrow] = Json::objectValue;
1340 jvParams[jss::escrow][jss::owner] = alice.human();
1341 jvParams[jss::escrow][jss::seq] = env.seq(alice) - 1;
1342 Json::Value const jrr = env.rpc(
1343 "json", "ledger_entry", to_string(jvParams))[jss::result];
1344 BEAST_EXPECT(
1345 jrr[jss::node][jss::Amount] == XRP(333).value().getText());
1346 escrowIndex = jrr[jss::index].asString();
1347 }
1348 {
1349 // Request the escrow by index.
1350 Json::Value jvParams;
1351 jvParams[jss::escrow] = escrowIndex;
1352 jvParams[jss::ledger_hash] = ledgerHash;
1353 Json::Value const jrr = env.rpc(
1354 "json", "ledger_entry", to_string(jvParams))[jss::result];
1355 BEAST_EXPECT(
1356 jrr[jss::node][jss::Amount] == XRP(333).value().getText());
1357 }
1358 {
1359 // Malformed escrow fields
1361 env,
1362 jss::escrow,
1363 {{jss::owner, "malformedOwner"}, {jss::seq, "malformedSeq"}});
1364 }
1365 }
1366
1367 void
1369 {
1370 testcase("Offer");
1371 using namespace test::jtx;
1372 Env env{*this};
1373 Account const alice{"alice"};
1374 Account const gw{"gateway"};
1375 auto const USD = gw["USD"];
1376 env.fund(XRP(10000), alice, gw);
1377 env.close();
1378
1379 env(offer(alice, USD(321), XRP(322)));
1380 env.close();
1381
1382 std::string const ledgerHash{to_string(env.closed()->info().hash)};
1383 std::string offerIndex;
1384 {
1385 // Request the offer using owner and sequence.
1386 Json::Value jvParams;
1387 jvParams[jss::offer] = Json::objectValue;
1388 jvParams[jss::offer][jss::account] = alice.human();
1389 jvParams[jss::offer][jss::seq] = env.seq(alice) - 1;
1390 jvParams[jss::ledger_hash] = ledgerHash;
1391 Json::Value const jrr = env.rpc(
1392 "json", "ledger_entry", to_string(jvParams))[jss::result];
1393 BEAST_EXPECT(jrr[jss::node][jss::TakerGets] == "322000000");
1394 offerIndex = jrr[jss::index].asString();
1395 }
1396 {
1397 // Request the offer using its index.
1398 Json::Value jvParams;
1399 jvParams[jss::offer] = offerIndex;
1400 Json::Value const jrr = env.rpc(
1401 "json", "ledger_entry", to_string(jvParams))[jss::result];
1402 BEAST_EXPECT(jrr[jss::node][jss::TakerGets] == "322000000");
1403 }
1404
1405 {
1406 // Malformed offer fields
1408 env,
1409 jss::offer,
1410 {{jss::account, "malformedAddress"},
1411 {jss::seq, "malformedRequest"}});
1412 }
1413 }
1414
1415 void
1417 {
1418 testcase("Pay Chan");
1419 using namespace test::jtx;
1420 using namespace std::literals::chrono_literals;
1421 Env env{*this};
1422 Account const alice{"alice"};
1423
1424 env.fund(XRP(10000), alice);
1425 env.close();
1426
1427 // Lambda to create a PayChan.
1428 auto payChanCreate = [](test::jtx::Account const& account,
1429 test::jtx::Account const& to,
1430 STAmount const& amount,
1431 NetClock::duration const& settleDelay,
1432 PublicKey const& pk) {
1433 Json::Value jv;
1434 jv[jss::TransactionType] = jss::PaymentChannelCreate;
1435 jv[jss::Account] = account.human();
1436 jv[jss::Destination] = to.human();
1437 jv[jss::Amount] = amount.getJson(JsonOptions::none);
1438 jv[sfSettleDelay.jsonName] = settleDelay.count();
1439 jv[sfPublicKey.jsonName] = strHex(pk.slice());
1440 return jv;
1441 };
1442
1443 env(payChanCreate(alice, env.master, XRP(57), 18s, alice.pk()));
1444 env.close();
1445
1446 std::string const ledgerHash{to_string(env.closed()->info().hash)};
1447
1448 uint256 const payChanIndex{
1449 keylet::payChan(alice, env.master, env.seq(alice) - 1).key};
1450 {
1451 // Request the payment channel using its index.
1452 Json::Value jvParams;
1453 jvParams[jss::payment_channel] = to_string(payChanIndex);
1454 jvParams[jss::ledger_hash] = ledgerHash;
1455 Json::Value const jrr = env.rpc(
1456 "json", "ledger_entry", to_string(jvParams))[jss::result];
1457 BEAST_EXPECT(jrr[jss::node][sfAmount.jsonName] == "57000000");
1458 BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "0");
1459 BEAST_EXPECT(jrr[jss::node][sfSettleDelay.jsonName] == 18);
1460 }
1461 {
1462 // Request an index that is not a payment channel.
1463 Json::Value jvParams;
1464 jvParams[jss::payment_channel] = ledgerHash;
1465 jvParams[jss::ledger_hash] = ledgerHash;
1466 Json::Value const jrr = env.rpc(
1467 "json", "ledger_entry", to_string(jvParams))[jss::result];
1468 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
1469 }
1470
1471 {
1472 // Malformed paychan field
1473 runLedgerEntryTest(env, jss::payment_channel);
1474 }
1475 }
1476
1477 void
1479 {
1480 testcase("RippleState");
1481 using namespace test::jtx;
1482 Env env{*this};
1483 Account const alice{"alice"};
1484 Account const gw{"gateway"};
1485 auto const USD = gw["USD"];
1486 env.fund(XRP(10000), alice, gw);
1487 env.close();
1488
1489 env.trust(USD(999), alice);
1490 env.close();
1491
1492 env(pay(gw, alice, USD(97)));
1493 env.close();
1494
1495 // check both aliases
1496 for (auto const& fieldName : {jss::ripple_state, jss::state})
1497 {
1498 std::string const ledgerHash{to_string(env.closed()->info().hash)};
1499 {
1500 // Request the trust line using the accounts and currency.
1501 Json::Value jvParams;
1502 jvParams[fieldName] = Json::objectValue;
1503 jvParams[fieldName][jss::accounts] = Json::arrayValue;
1504 jvParams[fieldName][jss::accounts][0u] = alice.human();
1505 jvParams[fieldName][jss::accounts][1u] = gw.human();
1506 jvParams[fieldName][jss::currency] = "USD";
1507 jvParams[jss::ledger_hash] = ledgerHash;
1508 Json::Value const jrr = env.rpc(
1509 "json", "ledger_entry", to_string(jvParams))[jss::result];
1510 BEAST_EXPECT(
1511 jrr[jss::node][sfBalance.jsonName][jss::value] == "-97");
1512 BEAST_EXPECT(
1513 jrr[jss::node][sfHighLimit.jsonName][jss::value] == "999");
1514 }
1515 {
1516 // test basic malformed scenarios
1518 env,
1519 fieldName,
1520 {
1521 {jss::accounts, "malformedRequest"},
1522 {jss::currency, "malformedCurrency"},
1523 });
1524 }
1525 {
1526 // ripple_state one of the accounts is missing.
1527 Json::Value jvParams;
1528 jvParams[fieldName] = Json::objectValue;
1529 jvParams[fieldName][jss::accounts] = Json::arrayValue;
1530 jvParams[fieldName][jss::accounts][0u] = alice.human();
1531 jvParams[fieldName][jss::currency] = "USD";
1532 jvParams[jss::ledger_hash] = ledgerHash;
1533 Json::Value const jrr = env.rpc(
1534 "json", "ledger_entry", to_string(jvParams))[jss::result];
1536 jrr,
1537 "malformedRequest",
1538 "Invalid field 'accounts', not length-2 array of "
1539 "Accounts.");
1540 }
1541 {
1542 // ripple_state more than 2 accounts.
1543 Json::Value jvParams;
1544 jvParams[fieldName] = Json::objectValue;
1545 jvParams[fieldName][jss::accounts] = Json::arrayValue;
1546 jvParams[fieldName][jss::accounts][0u] = alice.human();
1547 jvParams[fieldName][jss::accounts][1u] = gw.human();
1548 jvParams[fieldName][jss::accounts][2u] = alice.human();
1549 jvParams[fieldName][jss::currency] = "USD";
1550 jvParams[jss::ledger_hash] = ledgerHash;
1551 Json::Value const jrr = env.rpc(
1552 "json", "ledger_entry", to_string(jvParams))[jss::result];
1554 jrr,
1555 "malformedRequest",
1556 "Invalid field 'accounts', not length-2 array of "
1557 "Accounts.");
1558 }
1559 {
1560 // ripple_state account[0] / account[1] is not an account.
1561 Json::Value jvParams;
1562 jvParams[fieldName] = Json::objectValue;
1563 auto tryField = [&](Json::Value badAccount) -> void {
1564 {
1565 // account[0]
1566 jvParams[fieldName][jss::accounts] = Json::arrayValue;
1567 jvParams[fieldName][jss::accounts][0u] = badAccount;
1568 jvParams[fieldName][jss::accounts][1u] = gw.human();
1569 jvParams[fieldName][jss::currency] = "USD";
1570
1571 Json::Value const jrr = env.rpc(
1572 "json",
1573 "ledger_entry",
1574 to_string(jvParams))[jss::result];
1576 jrr,
1577 "malformedAddress",
1579 jss::accounts, "array of Accounts"));
1580 }
1581
1582 {
1583 // account[1]
1584 jvParams[fieldName][jss::accounts] = Json::arrayValue;
1585 jvParams[fieldName][jss::accounts][0u] = alice.human();
1586 jvParams[fieldName][jss::accounts][1u] = badAccount;
1587 jvParams[fieldName][jss::currency] = "USD";
1588
1589 Json::Value const jrr = env.rpc(
1590 "json",
1591 "ledger_entry",
1592 to_string(jvParams))[jss::result];
1594 jrr,
1595 "malformedAddress",
1597 jss::accounts, "array of Accounts"));
1598 }
1599 };
1600
1601 auto const& badValues = getBadValues(FieldType::AccountField);
1602 for (auto const& value : badValues)
1603 {
1604 tryField(value);
1605 }
1606 tryField(Json::nullValue);
1607 }
1608 {
1609 // ripple_state account[0] == account[1].
1610 Json::Value jvParams;
1611 jvParams[fieldName] = Json::objectValue;
1612 jvParams[fieldName][jss::accounts] = Json::arrayValue;
1613 jvParams[fieldName][jss::accounts][0u] = alice.human();
1614 jvParams[fieldName][jss::accounts][1u] = alice.human();
1615 jvParams[fieldName][jss::currency] = "USD";
1616 jvParams[jss::ledger_hash] = ledgerHash;
1617 Json::Value const jrr = env.rpc(
1618 "json", "ledger_entry", to_string(jvParams))[jss::result];
1620 jrr,
1621 "malformedRequest",
1622 "Cannot have a trustline to self.");
1623 }
1624 }
1625 }
1626
1627 void
1629 {
1630 testcase("Ticket");
1631 using namespace test::jtx;
1632 Env env{*this};
1633 env.close();
1634
1635 // Create two tickets.
1636 std::uint32_t const tkt1{env.seq(env.master) + 1};
1637 env(ticket::create(env.master, 2));
1638 env.close();
1639
1640 std::string const ledgerHash{to_string(env.closed()->info().hash)};
1641 // Request four tickets: one before the first one we created, the
1642 // two created tickets, and the ticket that would come after the
1643 // last created ticket.
1644 {
1645 // Not a valid ticket requested by index.
1646 Json::Value jvParams;
1647 jvParams[jss::ticket] =
1648 to_string(getTicketIndex(env.master, tkt1 - 1));
1649 jvParams[jss::ledger_hash] = ledgerHash;
1650 Json::Value const jrr = env.rpc(
1651 "json", "ledger_entry", to_string(jvParams))[jss::result];
1652 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
1653 }
1654 {
1655 // First real ticket requested by index.
1656 Json::Value jvParams;
1657 jvParams[jss::ticket] = to_string(getTicketIndex(env.master, tkt1));
1658 jvParams[jss::ledger_hash] = ledgerHash;
1659 Json::Value const jrr = env.rpc(
1660 "json", "ledger_entry", to_string(jvParams))[jss::result];
1661 BEAST_EXPECT(
1662 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Ticket);
1663 BEAST_EXPECT(jrr[jss::node][sfTicketSequence.jsonName] == tkt1);
1664 }
1665 {
1666 // Second real ticket requested by account and sequence.
1667 Json::Value jvParams;
1668 jvParams[jss::ticket] = Json::objectValue;
1669 jvParams[jss::ticket][jss::account] = env.master.human();
1670 jvParams[jss::ticket][jss::ticket_seq] = tkt1 + 1;
1671 jvParams[jss::ledger_hash] = ledgerHash;
1672 Json::Value const jrr = env.rpc(
1673 "json", "ledger_entry", to_string(jvParams))[jss::result];
1674 BEAST_EXPECT(
1675 jrr[jss::node][jss::index] ==
1676 to_string(getTicketIndex(env.master, tkt1 + 1)));
1677 }
1678 {
1679 // Not a valid ticket requested by account and sequence.
1680 Json::Value jvParams;
1681 jvParams[jss::ticket] = Json::objectValue;
1682 jvParams[jss::ticket][jss::account] = env.master.human();
1683 jvParams[jss::ticket][jss::ticket_seq] = tkt1 + 2;
1684 jvParams[jss::ledger_hash] = ledgerHash;
1685 Json::Value const jrr = env.rpc(
1686 "json", "ledger_entry", to_string(jvParams))[jss::result];
1687 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
1688 }
1689 {
1690 // Request a ticket using an account root entry.
1691 Json::Value jvParams;
1692 jvParams[jss::ticket] = to_string(keylet::account(env.master).key);
1693 jvParams[jss::ledger_hash] = ledgerHash;
1694 Json::Value const jrr = env.rpc(
1695 "json", "ledger_entry", to_string(jvParams))[jss::result];
1697 jrr, "unexpectedLedgerType", "Unexpected ledger type.");
1698 }
1699
1700 {
1701 // test basic malformed scenarios
1703 env,
1704 jss::ticket,
1705 {
1706 {jss::account, "malformedAddress"},
1707 {jss::ticket_seq, "malformedRequest"},
1708 });
1709 }
1710 }
1711
1712 void
1714 {
1715 testcase("DID");
1716 using namespace test::jtx;
1717 using namespace std::literals::chrono_literals;
1718 Env env{*this};
1719 Account const alice{"alice"};
1720
1721 env.fund(XRP(10000), alice);
1722 env.close();
1723
1724 // Lambda to create a DID.
1725 auto didCreate = [](test::jtx::Account const& account) {
1726 Json::Value jv;
1727 jv[jss::TransactionType] = jss::DIDSet;
1728 jv[jss::Account] = account.human();
1729 jv[sfDIDDocument.jsonName] = strHex(std::string{"data"});
1730 jv[sfURI.jsonName] = strHex(std::string{"uri"});
1731 return jv;
1732 };
1733
1734 env(didCreate(alice));
1735 env.close();
1736
1737 std::string const ledgerHash{to_string(env.closed()->info().hash)};
1738
1739 {
1740 // Request the DID using its index.
1741 Json::Value jvParams;
1742 jvParams[jss::did] = alice.human();
1743 jvParams[jss::ledger_hash] = ledgerHash;
1744 Json::Value const jrr = env.rpc(
1745 "json", "ledger_entry", to_string(jvParams))[jss::result];
1746 BEAST_EXPECT(
1747 jrr[jss::node][sfDIDDocument.jsonName] ==
1748 strHex(std::string{"data"}));
1749 BEAST_EXPECT(
1750 jrr[jss::node][sfURI.jsonName] == strHex(std::string{"uri"}));
1751 }
1752 {
1753 // Request an index that is not a DID.
1754 Json::Value jvParams;
1755 jvParams[jss::did] = env.master.human();
1756 jvParams[jss::ledger_hash] = ledgerHash;
1757 Json::Value const jrr = env.rpc(
1758 "json", "ledger_entry", to_string(jvParams))[jss::result];
1759 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
1760 }
1761 {
1762 // Malformed DID index
1763 Json::Value jvParams;
1765 env,
1766 jvParams,
1767 jss::did,
1769 "malformedAddress");
1770 }
1771 }
1772
1773 void
1775 {
1776 testcase("Invalid Oracle Ledger Entry");
1777 using namespace ripple::test::jtx;
1778 using namespace ripple::test::jtx::oracle;
1779
1780 Env env(*this);
1781 Account const owner("owner");
1782 env.fund(XRP(1'000), owner);
1783 Oracle oracle(
1784 env,
1785 {.owner = owner,
1786 .fee = static_cast<int>(env.current()->fees().base.drops())});
1787
1788 {
1789 // test basic malformed scenarios
1791 env,
1792 jss::oracle,
1793 {
1794 {jss::account, "malformedAccount"},
1795 {jss::oracle_document_id, "malformedDocumentID"},
1796 });
1797 }
1798 }
1799
1800 void
1802 {
1803 testcase("Oracle Ledger Entry");
1804 using namespace ripple::test::jtx;
1805 using namespace ripple::test::jtx::oracle;
1806
1807 Env env(*this);
1808 auto const baseFee =
1809 static_cast<int>(env.current()->fees().base.drops());
1810 std::vector<AccountID> accounts;
1812 for (int i = 0; i < 10; ++i)
1813 {
1814 Account const owner(std::string("owner") + std::to_string(i));
1815 env.fund(XRP(1'000), owner);
1816 // different accounts can have the same asset pair
1817 Oracle oracle(
1818 env, {.owner = owner, .documentID = i, .fee = baseFee});
1819 accounts.push_back(owner.id());
1820 oracles.push_back(oracle.documentID());
1821 // same account can have different asset pair
1822 Oracle oracle1(
1823 env, {.owner = owner, .documentID = i + 10, .fee = baseFee});
1824 accounts.push_back(owner.id());
1825 oracles.push_back(oracle1.documentID());
1826 }
1827 for (int i = 0; i < accounts.size(); ++i)
1828 {
1829 auto const jv = [&]() {
1830 // document id is uint32
1831 if (i % 2)
1832 return Oracle::ledgerEntry(env, accounts[i], oracles[i]);
1833 // document id is string
1834 return Oracle::ledgerEntry(
1835 env, accounts[i], std::to_string(oracles[i]));
1836 }();
1837 try
1838 {
1839 BEAST_EXPECT(
1840 jv[jss::node][jss::Owner] == to_string(accounts[i]));
1841 }
1842 catch (...)
1843 {
1844 fail();
1845 }
1846 }
1847 }
1848
1849 void
1851 {
1852 testcase("MPT");
1853 using namespace test::jtx;
1854 using namespace std::literals::chrono_literals;
1855 Env env{*this};
1856 Account const alice{"alice"};
1857 Account const bob("bob");
1858
1859 MPTTester mptAlice(env, alice, {.holders = {bob}});
1860 mptAlice.create(
1861 {.transferFee = 10,
1862 .metadata = "123",
1863 .ownerCount = 1,
1866 mptAlice.authorize({.account = bob, .holderCount = 1});
1867
1868 std::string const ledgerHash{to_string(env.closed()->info().hash)};
1869
1870 std::string const badMptID =
1871 "00000193B9DDCAF401B5B3B26875986043F82CD0D13B4315";
1872 {
1873 // Request the MPTIssuance using its MPTIssuanceID.
1874 Json::Value jvParams;
1875 jvParams[jss::mpt_issuance] = strHex(mptAlice.issuanceID());
1876 jvParams[jss::ledger_hash] = ledgerHash;
1877 Json::Value const jrr = env.rpc(
1878 "json", "ledger_entry", to_string(jvParams))[jss::result];
1879 BEAST_EXPECT(
1880 jrr[jss::node][sfMPTokenMetadata.jsonName] ==
1881 strHex(std::string{"123"}));
1882 BEAST_EXPECT(
1883 jrr[jss::node][jss::mpt_issuance_id] ==
1884 strHex(mptAlice.issuanceID()));
1885 }
1886 {
1887 // Request an index that is not a MPTIssuance.
1888 Json::Value jvParams;
1889 jvParams[jss::mpt_issuance] = badMptID;
1890 jvParams[jss::ledger_hash] = ledgerHash;
1891 Json::Value const jrr = env.rpc(
1892 "json", "ledger_entry", to_string(jvParams))[jss::result];
1893 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
1894 }
1895 {
1896 // Request the MPToken using its owner + mptIssuanceID.
1897 Json::Value jvParams;
1898 jvParams[jss::mptoken] = Json::objectValue;
1899 jvParams[jss::mptoken][jss::account] = bob.human();
1900 jvParams[jss::mptoken][jss::mpt_issuance_id] =
1901 strHex(mptAlice.issuanceID());
1902 jvParams[jss::ledger_hash] = ledgerHash;
1903 Json::Value const jrr = env.rpc(
1904 "json", "ledger_entry", to_string(jvParams))[jss::result];
1905 BEAST_EXPECT(
1906 jrr[jss::node][sfMPTokenIssuanceID.jsonName] ==
1907 strHex(mptAlice.issuanceID()));
1908 }
1909 {
1910 // Request the MPToken using a bad mptIssuanceID.
1911 Json::Value jvParams;
1912 jvParams[jss::mptoken] = Json::objectValue;
1913 jvParams[jss::mptoken][jss::account] = bob.human();
1914 jvParams[jss::mptoken][jss::mpt_issuance_id] = badMptID;
1915 jvParams[jss::ledger_hash] = ledgerHash;
1916 Json::Value const jrr = env.rpc(
1917 "json", "ledger_entry", to_string(jvParams))[jss::result];
1918 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
1919 }
1920 {
1921 // Malformed MPTIssuance index
1922 Json::Value jvParams;
1924 env,
1925 jvParams,
1926 jss::mptoken,
1928 "malformedRequest");
1929 }
1930 }
1931
1932 void
1934 {
1935 testcase("PermissionedDomain");
1936
1937 using namespace test::jtx;
1938
1939 Env env(*this, testable_amendments() | featurePermissionedDomains);
1940 Account const issuer{"issuer"};
1941 Account const alice{"alice"};
1942 Account const bob{"bob"};
1943
1944 env.fund(XRP(5000), issuer, alice, bob);
1945 env.close();
1946
1947 auto const seq = env.seq(alice);
1948 env(pdomain::setTx(alice, {{alice, "first credential"}}));
1949 env.close();
1950 auto const objects = pdomain::getObjects(alice, env);
1951 if (!BEAST_EXPECT(objects.size() == 1))
1952 return;
1953
1954 {
1955 // Succeed
1956 Json::Value params;
1957 params[jss::ledger_index] = jss::validated;
1958 params[jss::permissioned_domain][jss::account] = alice.human();
1959 params[jss::permissioned_domain][jss::seq] = seq;
1960 auto jv = env.rpc("json", "ledger_entry", to_string(params));
1961 BEAST_EXPECT(
1962 jv.isObject() && jv.isMember(jss::result) &&
1963 !jv[jss::result].isMember(jss::error) &&
1964 jv[jss::result].isMember(jss::node) &&
1965 jv[jss::result][jss::node].isMember(
1966 sfLedgerEntryType.jsonName) &&
1967 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
1968 jss::PermissionedDomain);
1969
1970 std::string const pdIdx = jv[jss::result][jss::index].asString();
1971 BEAST_EXPECT(
1972 strHex(keylet::permissionedDomain(alice, seq).key) == pdIdx);
1973
1974 params.clear();
1975 params[jss::ledger_index] = jss::validated;
1976 params[jss::permissioned_domain] = pdIdx;
1977 jv = env.rpc("json", "ledger_entry", to_string(params));
1978 BEAST_EXPECT(
1979 jv.isObject() && jv.isMember(jss::result) &&
1980 !jv[jss::result].isMember(jss::error) &&
1981 jv[jss::result].isMember(jss::node) &&
1982 jv[jss::result][jss::node].isMember(
1983 sfLedgerEntryType.jsonName) &&
1984 jv[jss::result][jss::node][sfLedgerEntryType.jsonName] ==
1985 jss::PermissionedDomain);
1986 }
1987
1988 {
1989 // Fail, invalid permissioned domain index
1990 Json::Value params;
1991 params[jss::ledger_index] = jss::validated;
1992 params[jss::permissioned_domain] =
1993 "12F1F1F1F180D67377B2FAB292A31C922470326268D2B9B74CD1E582645B9A"
1994 "DE";
1995 auto const jrr = env.rpc("json", "ledger_entry", to_string(params));
1997 jrr[jss::result], "entryNotFound", "Entry not found.");
1998 }
1999 {
2000 // test basic malformed scenarios
2002 env,
2003 jss::permissioned_domain,
2004 {
2005 {jss::account, "malformedAddress"},
2006 {jss::seq, "malformedRequest"},
2007 });
2008 }
2009 }
2010
2011 void
2013 {
2014 testcase("command-line");
2015 using namespace test::jtx;
2016
2017 Env env{*this};
2018 Account const alice{"alice"};
2019 env.fund(XRP(10000), alice);
2020 env.close();
2021
2022 auto const checkId = keylet::check(env.master, env.seq(env.master));
2023
2024 env(check::create(env.master, alice, XRP(100)));
2025 env.close();
2026
2027 std::string const ledgerHash{to_string(env.closed()->info().hash)};
2028 {
2029 // Request a check.
2030 Json::Value const jrr =
2031 env.rpc("ledger_entry", to_string(checkId.key))[jss::result];
2032 BEAST_EXPECT(
2033 jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Check);
2034 BEAST_EXPECT(jrr[jss::node][sfSendMax.jsonName] == "100000000");
2035 }
2036 }
2037
2038public:
2039 void
2062};
2063
2066{
2067 void
2069 Json::Value const& jv,
2070 std::string const& err,
2071 std::string const& msg)
2072 {
2073 if (BEAST_EXPECT(jv.isMember(jss::status)))
2074 BEAST_EXPECT(jv[jss::status] == "error");
2075 if (BEAST_EXPECT(jv.isMember(jss::error)))
2076 BEAST_EXPECT(jv[jss::error] == err);
2077 if (msg.empty())
2078 {
2079 BEAST_EXPECT(
2080 jv[jss::error_message] == Json::nullValue ||
2081 jv[jss::error_message] == "");
2082 }
2083 else if (BEAST_EXPECT(jv.isMember(jss::error_message)))
2084 BEAST_EXPECT(jv[jss::error_message] == msg);
2085 }
2086
2087 void
2089 {
2090 testcase("ledger_entry: bridge");
2091 using namespace test::jtx;
2092
2093 Env mcEnv{*this, features};
2094 Env scEnv(*this, envconfig(), features);
2095
2096 createBridgeObjects(mcEnv, scEnv);
2097
2098 std::string const ledgerHash{to_string(mcEnv.closed()->info().hash)};
2099 std::string bridge_index;
2100 Json::Value mcBridge;
2101 {
2102 // request the bridge via RPC
2103 Json::Value jvParams;
2104 jvParams[jss::bridge_account] = mcDoor.human();
2105 jvParams[jss::bridge] = jvb;
2106 Json::Value const jrr = mcEnv.rpc(
2107 "json", "ledger_entry", to_string(jvParams))[jss::result];
2108
2109 BEAST_EXPECT(jrr.isMember(jss::node));
2110 auto r = jrr[jss::node];
2111
2112 BEAST_EXPECT(r.isMember(jss::Account));
2113 BEAST_EXPECT(r[jss::Account] == mcDoor.human());
2114
2115 BEAST_EXPECT(r.isMember(jss::Flags));
2116
2117 BEAST_EXPECT(r.isMember(sfLedgerEntryType.jsonName));
2118 BEAST_EXPECT(r[sfLedgerEntryType.jsonName] == jss::Bridge);
2119
2120 // we not created an account yet
2121 BEAST_EXPECT(r.isMember(sfXChainAccountCreateCount.jsonName));
2122 BEAST_EXPECT(r[sfXChainAccountCreateCount.jsonName].asInt() == 0);
2123
2124 // we have not claimed a locking chain tx yet
2125 BEAST_EXPECT(r.isMember(sfXChainAccountClaimCount.jsonName));
2126 BEAST_EXPECT(r[sfXChainAccountClaimCount.jsonName].asInt() == 0);
2127
2128 BEAST_EXPECT(r.isMember(jss::index));
2129 bridge_index = r[jss::index].asString();
2130 mcBridge = r;
2131 }
2132 {
2133 // request the bridge via RPC by index
2134 Json::Value jvParams;
2135 jvParams[jss::index] = bridge_index;
2136 Json::Value const jrr = mcEnv.rpc(
2137 "json", "ledger_entry", to_string(jvParams))[jss::result];
2138
2139 BEAST_EXPECT(jrr.isMember(jss::node));
2140 BEAST_EXPECT(jrr[jss::node] == mcBridge);
2141 }
2142 {
2143 // swap door accounts and make sure we get an error value
2144 Json::Value jvParams;
2145 // Sidechain door account is "master", not scDoor
2146 jvParams[jss::bridge_account] = Account::master.human();
2147 jvParams[jss::bridge] = jvb;
2148 jvParams[jss::ledger_hash] = ledgerHash;
2149 Json::Value const jrr = mcEnv.rpc(
2150 "json", "ledger_entry", to_string(jvParams))[jss::result];
2151
2152 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
2153 }
2154 {
2155 // create two claim ids and verify that the bridge counter was
2156 // incremented
2158 mcEnv.close();
2160 mcEnv.close();
2161
2162 // request the bridge via RPC
2163 Json::Value jvParams;
2164 jvParams[jss::bridge_account] = mcDoor.human();
2165 jvParams[jss::bridge] = jvb;
2166 Json::Value const jrr = mcEnv.rpc(
2167 "json", "ledger_entry", to_string(jvParams))[jss::result];
2168
2169 BEAST_EXPECT(jrr.isMember(jss::node));
2170 auto r = jrr[jss::node];
2171
2172 // we executed two create claim id txs
2173 BEAST_EXPECT(r.isMember(sfXChainClaimID.jsonName));
2174 BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 2);
2175 }
2176 }
2177
2178 void
2180 {
2181 testcase("ledger_entry: xchain_claim_id");
2182 using namespace test::jtx;
2183
2184 Env mcEnv{*this, features};
2185 Env scEnv(*this, envconfig(), features);
2186
2187 createBridgeObjects(mcEnv, scEnv);
2188
2190 scEnv.close();
2192 scEnv.close();
2193
2194 std::string bridge_index;
2195 {
2196 // request the xchain_claim_id via RPC
2197 Json::Value jvParams;
2198 jvParams[jss::xchain_owned_claim_id] = jvXRPBridgeRPC;
2199 jvParams[jss::xchain_owned_claim_id][jss::xchain_owned_claim_id] =
2200 1;
2201 Json::Value const jrr = scEnv.rpc(
2202 "json", "ledger_entry", to_string(jvParams))[jss::result];
2203
2204 BEAST_EXPECT(jrr.isMember(jss::node));
2205 auto r = jrr[jss::node];
2206
2207 BEAST_EXPECT(r.isMember(jss::Account));
2208 BEAST_EXPECT(r[jss::Account] == scAlice.human());
2209 BEAST_EXPECT(
2210 r[sfLedgerEntryType.jsonName] == jss::XChainOwnedClaimID);
2211 BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 1);
2212 BEAST_EXPECT(r[sfOwnerNode.jsonName].asInt() == 0);
2213 }
2214
2215 {
2216 // request the xchain_claim_id via RPC
2217 Json::Value jvParams;
2218 jvParams[jss::xchain_owned_claim_id] = jvXRPBridgeRPC;
2219 jvParams[jss::xchain_owned_claim_id][jss::xchain_owned_claim_id] =
2220 2;
2221 Json::Value const jrr = scEnv.rpc(
2222 "json", "ledger_entry", to_string(jvParams))[jss::result];
2223
2224 BEAST_EXPECT(jrr.isMember(jss::node));
2225 auto r = jrr[jss::node];
2226
2227 BEAST_EXPECT(r.isMember(jss::Account));
2228 BEAST_EXPECT(r[jss::Account] == scBob.human());
2229 BEAST_EXPECT(
2230 r[sfLedgerEntryType.jsonName] == jss::XChainOwnedClaimID);
2231 BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 2);
2232 BEAST_EXPECT(r[sfOwnerNode.jsonName].asInt() == 0);
2233 }
2234 }
2235
2236 void
2238 {
2239 testcase("ledger_entry: xchain_create_account_claim_id");
2240 using namespace test::jtx;
2241
2242 Env mcEnv{*this, features};
2243 Env scEnv(*this, envconfig(), features);
2244
2245 // note: signers.size() and quorum are both 5 in createBridgeObjects
2246 createBridgeObjects(mcEnv, scEnv);
2247
2248 auto scCarol =
2249 Account("scCarol"); // Don't fund it - it will be created with the
2250 // xchain transaction
2251 auto const amt = XRP(1000);
2253 mcAlice, jvb, scCarol, amt, reward));
2254 mcEnv.close();
2255
2256 // send less than quorum of attestations (otherwise funds are
2257 // immediately transferred and no "claim" object is created)
2258 size_t constexpr num_attest = 3;
2259 auto attestations = create_account_attestations(
2260 scAttester,
2261 jvb,
2262 mcAlice,
2263 amt,
2264 reward,
2265 payee,
2266 /*wasLockingChainSend*/ true,
2267 1,
2268 scCarol,
2269 signers,
2271 for (size_t i = 0; i < num_attest; ++i)
2272 {
2273 scEnv(attestations[i]);
2274 }
2275 scEnv.close();
2276
2277 {
2278 // request the create account claim_id via RPC
2279 Json::Value jvParams;
2280 jvParams[jss::xchain_owned_create_account_claim_id] =
2282 jvParams[jss::xchain_owned_create_account_claim_id]
2283 [jss::xchain_owned_create_account_claim_id] = 1;
2284 Json::Value const jrr = scEnv.rpc(
2285 "json", "ledger_entry", to_string(jvParams))[jss::result];
2286
2287 BEAST_EXPECT(jrr.isMember(jss::node));
2288 auto r = jrr[jss::node];
2289
2290 BEAST_EXPECT(r.isMember(jss::Account));
2291 BEAST_EXPECT(r[jss::Account] == Account::master.human());
2292
2293 BEAST_EXPECT(r.isMember(sfXChainAccountCreateCount.jsonName));
2294 BEAST_EXPECT(r[sfXChainAccountCreateCount.jsonName].asInt() == 1);
2295
2296 BEAST_EXPECT(
2297 r.isMember(sfXChainCreateAccountAttestations.jsonName));
2298 auto attest = r[sfXChainCreateAccountAttestations.jsonName];
2299 BEAST_EXPECT(attest.isArray());
2300 BEAST_EXPECT(attest.size() == 3);
2301 BEAST_EXPECT(attest[Json::Value::UInt(0)].isMember(
2302 sfXChainCreateAccountProofSig.jsonName));
2303 Json::Value a[num_attest];
2304 for (size_t i = 0; i < num_attest; ++i)
2305 {
2306 a[i] = attest[Json::Value::UInt(0)]
2307 [sfXChainCreateAccountProofSig.jsonName];
2308 BEAST_EXPECT(
2309 a[i].isMember(jss::Amount) &&
2310 a[i][jss::Amount].asInt() == 1000 * drop_per_xrp);
2311 BEAST_EXPECT(
2312 a[i].isMember(jss::Destination) &&
2313 a[i][jss::Destination] == scCarol.human());
2314 BEAST_EXPECT(
2315 a[i].isMember(sfAttestationSignerAccount.jsonName) &&
2317 signers.begin(), signers.end(), [&](signer const& s) {
2318 return a[i][sfAttestationSignerAccount.jsonName] ==
2319 s.account.human();
2320 }));
2321 BEAST_EXPECT(
2322 a[i].isMember(sfAttestationRewardAccount.jsonName) &&
2324 payee.begin(),
2325 payee.end(),
2326 [&](Account const& account) {
2327 return a[i][sfAttestationRewardAccount.jsonName] ==
2328 account.human();
2329 }));
2330 BEAST_EXPECT(
2331 a[i].isMember(sfWasLockingChainSend.jsonName) &&
2332 a[i][sfWasLockingChainSend.jsonName] == 1);
2333 BEAST_EXPECT(
2334 a[i].isMember(sfSignatureReward.jsonName) &&
2335 a[i][sfSignatureReward.jsonName].asInt() ==
2336 1 * drop_per_xrp);
2337 }
2338 }
2339
2340 // complete attestations quorum - CreateAccountClaimID should not be
2341 // present anymore
2342 for (size_t i = num_attest; i < UT_XCHAIN_DEFAULT_NUM_SIGNERS; ++i)
2343 {
2344 scEnv(attestations[i]);
2345 }
2346 scEnv.close();
2347 {
2348 // request the create account claim_id via RPC
2349 Json::Value jvParams;
2350 jvParams[jss::xchain_owned_create_account_claim_id] =
2352 jvParams[jss::xchain_owned_create_account_claim_id]
2353 [jss::xchain_owned_create_account_claim_id] = 1;
2354 Json::Value const jrr = scEnv.rpc(
2355 "json", "ledger_entry", to_string(jvParams))[jss::result];
2356 checkErrorValue(jrr, "entryNotFound", "Entry not found.");
2357 }
2358 }
2359
2360public:
2361 void
2368};
2369
2370BEAST_DEFINE_TESTSUITE(LedgerEntry, rpc, ripple);
2371BEAST_DEFINE_TESTSUITE(LedgerEntry_XChain, rpc, ripple);
2372
2373} // namespace test
2374} // namespace ripple
T any_of(T... args)
Lightweight wrapper to tag static string.
Definition json_value.h:44
constexpr char const * c_str() const
Definition json_value.h:57
Represents a JSON value.
Definition json_value.h:130
Json::UInt UInt
Definition json_value.h:137
Value & append(Value const &value)
Append value to array at the end.
UInt size() const
Number of values in array or object.
std::string toStyledString() const
void clear()
Remove all object members and array elements.
Int asInt() const
bool isString() const
bool isObject() const
Value removeMember(char const *key)
Remove and return the named member.
std::string asString() const
Returns the unquoted string value.
bool isNull() const
isNull() tests to see if this field is null.
bool isMember(char const *key) const
Return true if the object has a member named key.
A testsuite class.
Definition suite.h:52
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:152
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:530
A public key.
Definition PublicKey.h:43
void checkErrorValue(Json::Value const &jv, std::string const &err, std::string const &msg)
void run() override
Runs the suite.
void runLedgerEntryTest(test::jtx::Env &env, Json::StaticString const &parentField, source_location const location=source_location::current())
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, source_location const location=source_location::current())
void runLedgerEntryTest(test::jtx::Env &env, Json::StaticString const &parentField, std::vector< Subfield > const &subfields, source_location const location=source_location::current())
void checkErrorValue(Json::Value const &jv, std::string const &err, std::string const &msg, source_location const location=source_location::current())
Json::Value getCorrectValue(Json::StaticString fieldName)
void run() override
Runs the suite.
void testMalformedField(test::jtx::Env &env, Json::Value correctRequest, Json::StaticString const fieldName, FieldType const typeID, std::string const &expectedError, bool required=true, source_location const location=source_location::current())
Immutable cryptographic account descriptor.
Definition Account.h:20
AccountID id() const
Returns the Account ID.
Definition Account.h:92
static Account const master
The master account.
Definition Account.h:29
std::string const & human() const
Returns the human readable public key.
Definition Account.h:99
A transaction testing environment.
Definition Env.h:102
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition Env.cpp:97
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:250
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:312
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:103
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:302
Account const & master
Definition Env.h:106
NetClock::time_point now()
Returns the current network time.
Definition Env.h:265
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:772
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:271
void create(MPTCreate const &arg=MPTCreate{})
Definition mpt.cpp:68
Oracle class facilitates unit-testing of the Price Oracle feature.
Definition Oracle.h:101
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition rpc.h:16
T empty(T... args)
T end(T... args)
T find_if(T... args)
@ nullValue
'null' value
Definition json_value.h:19
@ arrayValue
array value (ordered list)
Definition json_value.h:25
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:26
std::string missing_field_message(std::string const &name)
Definition ErrorCodes.h:258
std::string expected_field_message(std::string const &name, std::string const &type)
Definition ErrorCodes.h:318
Keylet permissionedDomain(AccountID const &account, std::uint32_t seq) noexcept
Definition Indexes.cpp:551
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:165
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition Indexes.cpp:317
Keylet payChan(AccountID const &src, AccountID const &dst, std::uint32_t seq) noexcept
A PaymentChannel.
Definition Indexes.cpp:376
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:13
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:59
Json::Value set(jtx::Account const &account, jtx::Account const &authorize, std::vector< std::string > const &permissions)
Definition delegate.cpp:12
Json::Value auth(Account const &account, Account const &auth)
Preauthorize for deposit.
Definition deposit.cpp:13
Json::Value authCredentials(jtx::Account const &account, std::vector< AuthorizeCredentials > const &auth)
Definition deposit.cpp:35
Json::Value setTx(AccountID const &account, Credentials const &credentials, std::optional< uint256 > domain)
std::map< uint256, Json::Value > getObjects(Account const &account, Env &env, bool withType)
Json::Value create(Account const &account, std::uint32_t count)
Create one of more tickets.
Definition ticket.cpp:12
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:10
Json::Value sidechain_xchain_account_create(Account const &acc, Json::Value const &bridge, Account const &dst, AnyAmount const &amt, AnyAmount const &reward)
constexpr std::size_t UT_XCHAIN_DEFAULT_NUM_SIGNERS
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:11
JValueVec create_account_attestations(jtx::Account const &submittingAccount, Json::Value const &jvBridge, jtx::Account const &sendingAccount, jtx::AnyAmount const &sendingAmount, jtx::AnyAmount const &rewardAmount, std::vector< jtx::Account > const &rewardAccounts, bool wasLockingChainSend, std::uint64_t createCount, jtx::Account const &dst, std::vector< jtx::signer > const &signers, std::size_t const numAtts, std::size_t const fromIdx)
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:35
require_t required(Args const &... args)
Compose many condition functors into one.
Definition require.h:30
FeatureBitset testable_amendments()
Definition Env.h:55
Json::Value xchain_create_claim_id(Account const &acc, Json::Value const &bridge, STAmount const &reward, Account const &otherChainSource)
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:10
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
static uint256 ledgerHash(LedgerInfo const &info)
std::vector< std::pair< Json::StaticString, FieldType > > mappings
FieldType getFieldType(Json::StaticString fieldName)
std::string getTypeName(FieldType typeID)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
constexpr std::uint32_t asfDepositAuth
Definition TxFlags.h:66
constexpr std::uint32_t const tfMPTCanTransfer
Definition TxFlags.h:133
constexpr std::uint32_t const tfMPTCanTrade
Definition TxFlags.h:132
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:11
std::size_t constexpr maxCredentialsArraySize
The maximum number of credentials can be passed in array.
Definition Protocol.h:90
void forAllApiVersions(Fn const &fn, Args &&... args)
Definition ApiVersion.h:158
uint256 getTicketIndex(AccountID const &account, std::uint32_t uSequence)
Definition Indexes.cpp:137
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
constexpr std::uint32_t const tfMPTCanEscrow
Definition TxFlags.h:131
constexpr std::uint32_t const tfMPTRequireAuth
Definition TxFlags.h:130
constexpr std::uint32_t const tfMPTCanLock
Definition TxFlags.h:129
constexpr std::uint32_t const tfMPTCanClawback
Definition TxFlags.h:134
T push_back(T... args)
T reserve(T... args)
T size(T... args)
uint256 key
Definition Keylet.h:21
std::vector< Account > const payee
std::vector< signer > const signers
void createBridgeObjects(Env &mcEnv, Env &scEnv)
Set the sequence number on a JTx.
Definition seq.h:15
A signer in a SignerList.
Definition multisign.h:20
T to_string(T... args)