rippled
Loading...
Searching...
No Matches
AccountObjects_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2016 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <test/jtx.h>
21#include <test/jtx/AMM.h>
22#include <test/jtx/xchain_bridge.h>
23
24#include <xrpld/app/tx/detail/NFTokenMint.h>
25
26#include <xrpl/json/json_reader.h>
27#include <xrpl/json/json_value.h>
28#include <xrpl/json/to_string.h>
29#include <xrpl/protocol/jss.h>
30#include <xrpl/protocol/nft.h>
31
32#include <boost/utility/string_ref.hpp>
33
34#include <algorithm>
35
36namespace ripple {
37namespace test {
38
39static char const* bobs_account_objects[] = {
40 R"json({
41 "Account" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
42 "BookDirectory" : "50AD0A9E54D2B381288D535EB724E4275FFBF41580D28A925D038D7EA4C68000",
43 "BookNode" : "0",
44 "Flags" : 65536,
45 "LedgerEntryType" : "Offer",
46 "OwnerNode" : "0",
47 "Sequence" : 6,
48 "TakerGets" : {
49 "currency" : "USD",
50 "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
51 "value" : "1"
52 },
53 "TakerPays" : "100000000",
54 "index" : "29665262716C19830E26AEEC0916E476FC7D8EF195FF3B4F06829E64F82A3B3E"
55})json",
56 R"json({
57 "Balance" : {
58 "currency" : "USD",
59 "issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji",
60 "value" : "-1000"
61 },
62 "Flags" : 131072,
63 "HighLimit" : {
64 "currency" : "USD",
65 "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
66 "value" : "1000"
67 },
68 "HighNode" : "0",
69 "LedgerEntryType" : "RippleState",
70 "LowLimit" : {
71 "currency" : "USD",
72 "issuer" : "r9cZvwKU3zzuZK9JFovGg1JC5n7QiqNL8L",
73 "value" : "0"
74 },
75 "LowNode" : "0",
76 "index" : "D13183BCFFC9AAC9F96AEBB5F66E4A652AD1F5D10273AEB615478302BEBFD4A4"
77})json",
78 R"json({
79 "Balance" : {
80 "currency" : "USD",
81 "issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji",
82 "value" : "-1000"
83 },
84 "Flags" : 131072,
85 "HighLimit" : {
86 "currency" : "USD",
87 "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
88 "value" : "1000"
89 },
90 "HighNode" : "0",
91 "LedgerEntryType" : "RippleState",
92 "LowLimit" : {
93 "currency" : "USD",
94 "issuer" : "r32rQHyesiTtdWFU7UJVtff4nCR5SHCbJW",
95 "value" : "0"
96 },
97 "LowNode" : "0",
98 "index" : "D89BC239086183EB9458C396E643795C1134963E6550E682A190A5F021766D43"
99})json",
100 R"json({
101 "Account" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
102 "BookDirectory" : "B025997A323F5C3E03DDF1334471F5984ABDE31C59D463525D038D7EA4C68000",
103 "BookNode" : "0",
104 "Flags" : 65536,
105 "LedgerEntryType" : "Offer",
106 "OwnerNode" : "0",
107 "Sequence" : 7,
108 "TakerGets" : {
109 "currency" : "USD",
110 "issuer" : "r32rQHyesiTtdWFU7UJVtff4nCR5SHCbJW",
111 "value" : "1"
112 },
113 "TakerPays" : "100000000",
114 "index" : "F03ABE26CB8C5F4AFB31A86590BD25C64C5756FCE5CE9704C27AFE291A4A29A1"
115})json"};
116
118{
119public:
120 void
122 {
123 testcase("error cases");
124
125 using namespace jtx;
126 Env env(*this);
127
128 // test error on no account
129 {
130 Json::Value params;
131 auto resp = env.rpc("json", "account_objects", to_string(params));
132 BEAST_EXPECT(
133 resp[jss::result][jss::error_message] ==
134 "Missing field 'account'.");
135 }
136 // test account non-string
137 {
138 auto testInvalidAccountParam = [&](auto const& param) {
139 Json::Value params;
140 params[jss::account] = param;
141 auto jrr = env.rpc(
142 "json", "account_objects", to_string(params))[jss::result];
143 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
144 BEAST_EXPECT(
145 jrr[jss::error_message] == "Invalid field 'account'.");
146 };
147
148 testInvalidAccountParam(1);
149 testInvalidAccountParam(1.1);
150 testInvalidAccountParam(true);
151 testInvalidAccountParam(Json::Value(Json::nullValue));
152 testInvalidAccountParam(Json::Value(Json::objectValue));
153 testInvalidAccountParam(Json::Value(Json::arrayValue));
154 }
155 // test error on malformed account string.
156 {
157 Json::Value params;
158 params[jss::account] =
159 "n94JNrQYkDrpt62bbSR7nVEhdyAvcJXRAsjEkFYyqRkh9SUTYEqV";
160 auto resp = env.rpc("json", "account_objects", to_string(params));
161 BEAST_EXPECT(
162 resp[jss::result][jss::error_message] == "Account malformed.");
163 }
164 // test error on account that's not in the ledger.
165 {
166 Json::Value params;
167 params[jss::account] = Account{"bogie"}.human();
168 auto resp = env.rpc("json", "account_objects", to_string(params));
169 BEAST_EXPECT(
170 resp[jss::result][jss::error_message] == "Account not found.");
171 }
172 Account const bob{"bob"};
173 // test error on large ledger_index.
174 {
175 Json::Value params;
176 params[jss::account] = bob.human();
177 params[jss::ledger_index] = 10;
178 auto resp = env.rpc("json", "account_objects", to_string(params));
179 BEAST_EXPECT(
180 resp[jss::result][jss::error_message] == "ledgerNotFound");
181 }
182
183 env.fund(XRP(1000), bob);
184 // test error on type param not a string
185 {
186 Json::Value params;
187 params[jss::account] = bob.human();
188 params[jss::type] = 10;
189 auto resp = env.rpc("json", "account_objects", to_string(params));
190 BEAST_EXPECT(
191 resp[jss::result][jss::error_message] ==
192 "Invalid field 'type', not string.");
193 }
194 // test error on type param not a valid type
195 {
196 Json::Value params;
197 params[jss::account] = bob.human();
198 params[jss::type] = "expedited";
199 auto resp = env.rpc("json", "account_objects", to_string(params));
200 BEAST_EXPECT(
201 resp[jss::result][jss::error_message] ==
202 "Invalid field 'type'.");
203 }
204 // test error on limit -ve
205 {
206 Json::Value params;
207 params[jss::account] = bob.human();
208 params[jss::limit] = -1;
209 auto resp = env.rpc("json", "account_objects", to_string(params));
210 BEAST_EXPECT(
211 resp[jss::result][jss::error_message] ==
212 "Invalid field 'limit', not unsigned integer.");
213 }
214 // test errors on marker
215 {
216 Account const gw{"G"};
217 env.fund(XRP(1000), gw);
218 auto const USD = gw["USD"];
219 env.trust(USD(1000), bob);
220 env(pay(gw, bob, XRP(1)));
221 env(offer(bob, XRP(100), bob["USD"](1)), txflags(tfPassive));
222
223 Json::Value params;
224 params[jss::account] = bob.human();
225 params[jss::limit] = 1;
226 auto resp = env.rpc("json", "account_objects", to_string(params));
227
228 auto resume_marker = resp[jss::result][jss::marker];
229 std::string mark = to_string(resume_marker);
230 params[jss::marker] = 10;
231 resp = env.rpc("json", "account_objects", to_string(params));
232 BEAST_EXPECT(
233 resp[jss::result][jss::error_message] ==
234 "Invalid field 'marker', not string.");
235
236 params[jss::marker] = "This is a string with no comma";
237 resp = env.rpc("json", "account_objects", to_string(params));
238 BEAST_EXPECT(
239 resp[jss::result][jss::error_message] ==
240 "Invalid field 'marker'.");
241
242 params[jss::marker] = "This string has a comma, but is not hex";
243 resp = env.rpc("json", "account_objects", to_string(params));
244 BEAST_EXPECT(
245 resp[jss::result][jss::error_message] ==
246 "Invalid field 'marker'.");
247
248 params[jss::marker] = std::string(&mark[1U], 64);
249 resp = env.rpc("json", "account_objects", to_string(params));
250 BEAST_EXPECT(
251 resp[jss::result][jss::error_message] ==
252 "Invalid field 'marker'.");
253
254 params[jss::marker] = std::string(&mark[1U], 65);
255 resp = env.rpc("json", "account_objects", to_string(params));
256 BEAST_EXPECT(
257 resp[jss::result][jss::error_message] ==
258 "Invalid field 'marker'.");
259
260 params[jss::marker] = std::string(&mark[1U], 65) + "not hex";
261 resp = env.rpc("json", "account_objects", to_string(params));
262 BEAST_EXPECT(
263 resp[jss::result][jss::error_message] ==
264 "Invalid field 'marker'.");
265
266 // Should this be an error?
267 // A hex digit is absent from the end of marker.
268 // No account objects returned.
269 params[jss::marker] = std::string(&mark[1U], 128);
270 resp = env.rpc("json", "account_objects", to_string(params));
271 BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 0);
272 }
273 }
274
275 void
277 {
278 testcase("unsteppedThenStepped");
279
280 using namespace jtx;
281 Env env(*this);
282
283 Account const gw1{"G1"};
284 Account const gw2{"G2"};
285 Account const bob{"bob"};
286
287 auto const USD1 = gw1["USD"];
288 auto const USD2 = gw2["USD"];
289
290 env.fund(XRP(1000), gw1, gw2, bob);
291 env.trust(USD1(1000), bob);
292 env.trust(USD2(1000), bob);
293
294 env(pay(gw1, bob, USD1(1000)));
295 env(pay(gw2, bob, USD2(1000)));
296
297 env(offer(bob, XRP(100), bob["USD"](1)), txflags(tfPassive));
298 env(offer(bob, XRP(100), USD1(1)), txflags(tfPassive));
299
300 Json::Value bobj[4];
301 for (int i = 0; i < 4; ++i)
303
304 // test 'unstepped'
305 // i.e. request account objects without explicit limit/marker paging
306 {
307 Json::Value params;
308 params[jss::account] = bob.human();
309 auto resp = env.rpc("json", "account_objects", to_string(params));
310 BEAST_EXPECT(!resp.isMember(jss::marker));
311
312 BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 4);
313 for (int i = 0; i < 4; ++i)
314 {
315 auto& aobj = resp[jss::result][jss::account_objects][i];
316 aobj.removeMember("PreviousTxnID");
317 aobj.removeMember("PreviousTxnLgrSeq");
318 BEAST_EXPECT(aobj == bobj[i]);
319 }
320 }
321 // test request with type parameter as filter, unstepped
322 {
323 Json::Value params;
324 params[jss::account] = bob.human();
325 params[jss::type] = jss::state;
326 auto resp = env.rpc("json", "account_objects", to_string(params));
327 BEAST_EXPECT(!resp.isMember(jss::marker));
328
329 BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 2);
330 for (int i = 0; i < 2; ++i)
331 {
332 auto& aobj = resp[jss::result][jss::account_objects][i];
333 aobj.removeMember("PreviousTxnID");
334 aobj.removeMember("PreviousTxnLgrSeq");
335 BEAST_EXPECT(aobj == bobj[i + 1]);
336 }
337 }
338 // test stepped one-at-a-time with limit=1, resume from prev marker
339 {
340 Json::Value params;
341 params[jss::account] = bob.human();
342 params[jss::limit] = 1;
343 for (int i = 0; i < 4; ++i)
344 {
345 auto resp =
346 env.rpc("json", "account_objects", to_string(params));
347 auto& aobjs = resp[jss::result][jss::account_objects];
348 BEAST_EXPECT(aobjs.size() == 1);
349 auto& aobj = aobjs[0U];
350 if (i < 3)
351 BEAST_EXPECT(resp[jss::result][jss::limit] == 1);
352 else
353 BEAST_EXPECT(!resp[jss::result].isMember(jss::limit));
354
355 aobj.removeMember("PreviousTxnID");
356 aobj.removeMember("PreviousTxnLgrSeq");
357
358 BEAST_EXPECT(aobj == bobj[i]);
359
360 params[jss::marker] = resp[jss::result][jss::marker];
361 }
362 }
363 }
364
365 void
367 {
368 // The preceding test case, unsteppedThenStepped(), found a bug in the
369 // support for NFToken Pages. So we're leaving that test alone when
370 // adding tests to exercise NFTokenPages.
371 testcase("unsteppedThenSteppedWithNFTs");
372
373 using namespace jtx;
374 Env env(*this);
375
376 Account const gw1{"G1"};
377 Account const gw2{"G2"};
378 Account const bob{"bob"};
379
380 auto const USD1 = gw1["USD"];
381 auto const USD2 = gw2["USD"];
382
383 env.fund(XRP(1000), gw1, gw2, bob);
384 env.close();
385
386 // Check behavior if there are no account objects.
387 {
388 // Unpaged
389 Json::Value params;
390 params[jss::account] = bob.human();
391 auto resp = env.rpc("json", "account_objects", to_string(params));
392 BEAST_EXPECT(!resp.isMember(jss::marker));
393 BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 0);
394
395 // Limit == 1
396 params[jss::limit] = 1;
397 resp = env.rpc("json", "account_objects", to_string(params));
398 BEAST_EXPECT(!resp.isMember(jss::marker));
399 BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 0);
400 }
401
402 // Check behavior if there are only NFTokens.
403 env(token::mint(bob, 0u), txflags(tfTransferable));
404 env.close();
405
406 // test 'unstepped'
407 // i.e. request account objects without explicit limit/marker paging
408 Json::Value unpaged;
409 {
410 Json::Value params;
411 params[jss::account] = bob.human();
412 auto resp = env.rpc("json", "account_objects", to_string(params));
413 BEAST_EXPECT(!resp.isMember(jss::marker));
414
415 unpaged = resp[jss::result][jss::account_objects];
416 BEAST_EXPECT(unpaged.size() == 1);
417 }
418 // test request with type parameter as filter, unstepped
419 {
420 Json::Value params;
421 params[jss::account] = bob.human();
422 params[jss::type] = jss::nft_page;
423 auto resp = env.rpc("json", "account_objects", to_string(params));
424 BEAST_EXPECT(!resp.isMember(jss::marker));
425 Json::Value& aobjs = resp[jss::result][jss::account_objects];
426 BEAST_EXPECT(aobjs.size() == 1);
427 BEAST_EXPECT(
428 aobjs[0u][sfLedgerEntryType.jsonName] == jss::NFTokenPage);
429 BEAST_EXPECT(aobjs[0u][sfNFTokens.jsonName].size() == 1);
430 }
431 // test stepped one-at-a-time with limit=1, resume from prev marker
432 {
433 Json::Value params;
434 params[jss::account] = bob.human();
435 params[jss::limit] = 1;
436
437 Json::Value resp =
438 env.rpc("json", "account_objects", to_string(params));
439 Json::Value& aobjs = resp[jss::result][jss::account_objects];
440 BEAST_EXPECT(aobjs.size() == 1);
441 auto& aobj = aobjs[0U];
442 BEAST_EXPECT(!resp[jss::result].isMember(jss::limit));
443 BEAST_EXPECT(!resp[jss::result].isMember(jss::marker));
444
445 BEAST_EXPECT(aobj == unpaged[0u]);
446 }
447
448 // Add more objects in addition to the NFToken Page.
449 env.trust(USD1(1000), bob);
450 env.trust(USD2(1000), bob);
451
452 env(pay(gw1, bob, USD1(1000)));
453 env(pay(gw2, bob, USD2(1000)));
454
455 env(offer(bob, XRP(100), bob["USD"](1)), txflags(tfPassive));
456 env(offer(bob, XRP(100), USD1(1)), txflags(tfPassive));
457 env.close();
458
459 // test 'unstepped'
460 {
461 Json::Value params;
462 params[jss::account] = bob.human();
463 auto resp = env.rpc("json", "account_objects", to_string(params));
464 BEAST_EXPECT(!resp.isMember(jss::marker));
465
466 unpaged = resp[jss::result][jss::account_objects];
467 BEAST_EXPECT(unpaged.size() == 5);
468 }
469 // test request with type parameter as filter, unstepped
470 {
471 Json::Value params;
472 params[jss::account] = bob.human();
473 params[jss::type] = jss::nft_page;
474 auto resp = env.rpc("json", "account_objects", to_string(params));
475 BEAST_EXPECT(!resp.isMember(jss::marker));
476 Json::Value& aobjs = resp[jss::result][jss::account_objects];
477 BEAST_EXPECT(aobjs.size() == 1);
478 BEAST_EXPECT(
479 aobjs[0u][sfLedgerEntryType.jsonName] == jss::NFTokenPage);
480 BEAST_EXPECT(aobjs[0u][sfNFTokens.jsonName].size() == 1);
481 }
482 // test stepped one-at-a-time with limit=1, resume from prev marker
483 {
484 Json::Value params;
485 params[jss::account] = bob.human();
486 params[jss::limit] = 1;
487 for (int i = 0; i < 5; ++i)
488 {
489 Json::Value resp =
490 env.rpc("json", "account_objects", to_string(params));
491 Json::Value& aobjs = resp[jss::result][jss::account_objects];
492 BEAST_EXPECT(aobjs.size() == 1);
493 auto& aobj = aobjs[0U];
494 if (i < 4)
495 {
496 BEAST_EXPECT(resp[jss::result][jss::limit] == 1);
497 BEAST_EXPECT(resp[jss::result].isMember(jss::marker));
498 }
499 else
500 {
501 BEAST_EXPECT(!resp[jss::result].isMember(jss::limit));
502 BEAST_EXPECT(!resp[jss::result].isMember(jss::marker));
503 }
504
505 BEAST_EXPECT(aobj == unpaged[i]);
506
507 params[jss::marker] = resp[jss::result][jss::marker];
508 }
509 }
510
511 // Make sure things still work if there is more than 1 NFT Page.
512 for (int i = 0; i < 32; ++i)
513 {
514 env(token::mint(bob, 0u), txflags(tfTransferable));
515 env.close();
516 }
517 // test 'unstepped'
518 {
519 Json::Value params;
520 params[jss::account] = bob.human();
521 auto resp = env.rpc("json", "account_objects", to_string(params));
522 BEAST_EXPECT(!resp.isMember(jss::marker));
523
524 unpaged = resp[jss::result][jss::account_objects];
525 BEAST_EXPECT(unpaged.size() == 6);
526 }
527 // test request with type parameter as filter, unstepped
528 {
529 Json::Value params;
530 params[jss::account] = bob.human();
531 params[jss::type] = jss::nft_page;
532 auto resp = env.rpc("json", "account_objects", to_string(params));
533 BEAST_EXPECT(!resp.isMember(jss::marker));
534 Json::Value& aobjs = resp[jss::result][jss::account_objects];
535 BEAST_EXPECT(aobjs.size() == 2);
536 }
537 // test stepped one-at-a-time with limit=1, resume from prev marker
538 {
539 Json::Value params;
540 params[jss::account] = bob.human();
541 params[jss::limit] = 1;
542 for (int i = 0; i < 6; ++i)
543 {
544 Json::Value resp =
545 env.rpc("json", "account_objects", to_string(params));
546 Json::Value& aobjs = resp[jss::result][jss::account_objects];
547 BEAST_EXPECT(aobjs.size() == 1);
548 auto& aobj = aobjs[0U];
549 if (i < 5)
550 {
551 BEAST_EXPECT(resp[jss::result][jss::limit] == 1);
552 BEAST_EXPECT(resp[jss::result].isMember(jss::marker));
553 }
554 else
555 {
556 BEAST_EXPECT(!resp[jss::result].isMember(jss::limit));
557 BEAST_EXPECT(!resp[jss::result].isMember(jss::marker));
558 }
559
560 BEAST_EXPECT(aobj == unpaged[i]);
561
562 params[jss::marker] = resp[jss::result][jss::marker];
563 }
564 }
565 }
566
567 void
569 {
570 testcase("object types");
571
572 // Give gw a bunch of ledger objects and make sure we can retrieve
573 // them by type.
574 using namespace jtx;
575
576 Account const alice{"alice"};
577 Account const gw{"gateway"};
578 auto const USD = gw["USD"];
579
580 auto const features = testable_amendments() | featureXChainBridge |
581 featurePermissionedDomains;
582 Env env(*this, features);
583
584 // Make a lambda we can use to get "account_objects" easily.
585 auto acctObjs = [&env](
586 AccountID const& acct,
588 std::optional<std::uint16_t> limit = std::nullopt,
589 std::optional<std::string> marker = std::nullopt) {
590 Json::Value params;
591 params[jss::account] = to_string(acct);
592 if (type)
593 params[jss::type] = *type;
594 if (limit)
595 params[jss::limit] = *limit;
596 if (marker)
597 params[jss::marker] = *marker;
598 params[jss::ledger_index] = "validated";
599 return env.rpc("json", "account_objects", to_string(params));
600 };
601
602 // Make a lambda that easily identifies the size of account objects.
603 auto acctObjsIsSize = [](Json::Value const& resp, unsigned size) {
604 return resp[jss::result][jss::account_objects].isArray() &&
605 (resp[jss::result][jss::account_objects].size() == size);
606 };
607
608 // Make a lambda that checks if the response has error for invalid type
609 auto acctObjsTypeIsInvalid = [](Json::Value const& resp) {
610 return resp[jss::result].isMember(jss::error) &&
611 resp[jss::result][jss::error_message] ==
612 "Invalid field \'type\'.";
613 };
614
615 env.fund(XRP(10000), gw, alice);
616 env.close();
617
618 // Since the account is empty now, all account objects should come
619 // back empty.
620 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::account), 0));
621 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::check), 0));
622 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::deposit_preauth), 0));
623 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::escrow), 0));
624 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::nft_page), 0));
625 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::offer), 0));
626 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::payment_channel), 0));
627 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::signer_list), 0));
628 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::state), 0));
629 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::ticket), 0));
630 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::amm), 0));
631 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::did), 0));
632 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::permissioned_domain), 0));
633
634 // we expect invalid field type reported for the following types
635 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::amendments)));
636 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::directory)));
637 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::fee)));
638 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::hashes)));
639 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::NegativeUNL)));
640
641 // gw mints an NFT so we can find it.
642 uint256 const nftID{token::getNextID(env, gw, 0u, tfTransferable)};
643 env(token::mint(gw, 0u), txflags(tfTransferable));
644 env.close();
645 {
646 // Find the NFToken page and make sure it's the right one.
647 Json::Value const resp = acctObjs(gw, jss::nft_page);
648 BEAST_EXPECT(acctObjsIsSize(resp, 1));
649
650 auto const& nftPage = resp[jss::result][jss::account_objects][0u];
651 BEAST_EXPECT(nftPage[sfNFTokens.jsonName].size() == 1);
652 BEAST_EXPECT(
653 nftPage[sfNFTokens.jsonName][0u][sfNFToken.jsonName]
654 [sfNFTokenID.jsonName] == to_string(nftID));
655 }
656
657 // Set up a trust line so we can find it.
658 env.trust(USD(1000), alice);
659 env.close();
660 env(pay(gw, alice, USD(5)));
661 env.close();
662 {
663 // Find the trustline and make sure it's the right one.
664 Json::Value const resp = acctObjs(gw, jss::state);
665 BEAST_EXPECT(acctObjsIsSize(resp, 1));
666
667 auto const& state = resp[jss::result][jss::account_objects][0u];
668 BEAST_EXPECT(state[sfBalance.jsonName][jss::value].asInt() == -5);
669 BEAST_EXPECT(
670 state[sfHighLimit.jsonName][jss::value].asUInt() == 1000);
671 }
672 // gw writes a check for USD(10) to alice.
673 env(check::create(gw, alice, USD(10)));
674 env.close();
675 {
676 // Find the check.
677 Json::Value const resp = acctObjs(gw, jss::check);
678 BEAST_EXPECT(acctObjsIsSize(resp, 1));
679
680 auto const& check = resp[jss::result][jss::account_objects][0u];
681 BEAST_EXPECT(check[sfAccount.jsonName] == gw.human());
682 BEAST_EXPECT(check[sfDestination.jsonName] == alice.human());
683 BEAST_EXPECT(check[sfSendMax.jsonName][jss::value].asUInt() == 10);
684 }
685 // gw preauthorizes payments from alice.
686 env(deposit::auth(gw, alice));
687 env.close();
688 {
689 // Find the preauthorization.
690 Json::Value const resp = acctObjs(gw, jss::deposit_preauth);
691 BEAST_EXPECT(acctObjsIsSize(resp, 1));
692
693 auto const& preauth = resp[jss::result][jss::account_objects][0u];
694 BEAST_EXPECT(preauth[sfAccount.jsonName] == gw.human());
695 BEAST_EXPECT(preauth[sfAuthorize.jsonName] == alice.human());
696 }
697 {
698 // gw creates an escrow that we can look for in the ledger.
699 Json::Value jvEscrow;
700 jvEscrow[jss::TransactionType] = jss::EscrowCreate;
701 jvEscrow[jss::Account] = gw.human();
702 jvEscrow[jss::Destination] = gw.human();
703 jvEscrow[jss::Amount] = XRP(100).value().getJson(JsonOptions::none);
704 jvEscrow[sfFinishAfter.jsonName] =
705 env.now().time_since_epoch().count() + 1;
706 env(jvEscrow);
707 env.close();
708 }
709 {
710 // Find the escrow.
711 Json::Value const resp = acctObjs(gw, jss::escrow);
712 BEAST_EXPECT(acctObjsIsSize(resp, 1));
713
714 auto const& escrow = resp[jss::result][jss::account_objects][0u];
715 BEAST_EXPECT(escrow[sfAccount.jsonName] == gw.human());
716 BEAST_EXPECT(escrow[sfDestination.jsonName] == gw.human());
717 BEAST_EXPECT(escrow[sfAmount.jsonName].asUInt() == 100'000'000);
718 }
719
720 {
721 std::string const credentialType1 = "credential1";
722 Account issuer("issuer");
723 env.fund(XRP(5000), issuer);
724
725 // gw creates an PermissionedDomain.
726 env(pdomain::setTx(gw, {{issuer, credentialType1}}));
727 env.close();
728
729 // Find the PermissionedDomain.
730 Json::Value const resp = acctObjs(gw, jss::permissioned_domain);
731 BEAST_EXPECT(acctObjsIsSize(resp, 1));
732
733 auto const& permissionedDomain =
734 resp[jss::result][jss::account_objects][0u];
735 BEAST_EXPECT(
736 permissionedDomain.isMember(jss::Owner) &&
737 (permissionedDomain[jss::Owner] == gw.human()));
738 bool const check1 = BEAST_EXPECT(
739 permissionedDomain.isMember(jss::AcceptedCredentials) &&
740 permissionedDomain[jss::AcceptedCredentials].isArray() &&
741 (permissionedDomain[jss::AcceptedCredentials].size() == 1) &&
742 (permissionedDomain[jss::AcceptedCredentials][0u].isMember(
743 jss::Credential)));
744
745 if (check1)
746 {
747 auto const& credential =
748 permissionedDomain[jss::AcceptedCredentials][0u]
749 [jss::Credential];
750 BEAST_EXPECT(
751 credential.isMember(sfIssuer.jsonName) &&
752 (credential[sfIssuer.jsonName] == issuer.human()));
753 BEAST_EXPECT(
754 credential.isMember(sfCredentialType.jsonName) &&
755 (credential[sfCredentialType.jsonName] ==
756 strHex(credentialType1)));
757 }
758 }
759
760 {
761 // Create a bridge
763 Env scEnv(*this, envconfig(), features);
764 x.createScBridgeObjects(scEnv);
765
766 auto scEnvAcctObjs = [&](Account const& acct, char const* type) {
767 Json::Value params;
768 params[jss::account] = acct.human();
769 params[jss::type] = type;
770 params[jss::ledger_index] = "validated";
771 return scEnv.rpc("json", "account_objects", to_string(params));
772 };
773
774 Json::Value const resp =
775 scEnvAcctObjs(Account::master, jss::bridge);
776
777 BEAST_EXPECT(acctObjsIsSize(resp, 1));
778 auto const& acct_bridge =
779 resp[jss::result][jss::account_objects][0u];
780 BEAST_EXPECT(
781 acct_bridge[sfAccount.jsonName] == Account::master.human());
782 BEAST_EXPECT(
783 acct_bridge[sfLedgerEntryType.getJsonName()] == "Bridge");
784 BEAST_EXPECT(
785 acct_bridge[sfXChainClaimID.getJsonName()].asUInt() == 0);
786 BEAST_EXPECT(
787 acct_bridge[sfXChainAccountClaimCount.getJsonName()].asUInt() ==
788 0);
789 BEAST_EXPECT(
790 acct_bridge[sfXChainAccountCreateCount.getJsonName()]
791 .asUInt() == 0);
792 BEAST_EXPECT(
793 acct_bridge[sfMinAccountCreateAmount.getJsonName()].asUInt() ==
794 20000000);
795 BEAST_EXPECT(
796 acct_bridge[sfSignatureReward.getJsonName()].asUInt() ==
797 1000000);
798 BEAST_EXPECT(acct_bridge[sfXChainBridge.getJsonName()] == x.jvb);
799 }
800 {
801 // Alice and Bob create a xchain sequence number that we can look
802 // for in the ledger.
804 Env scEnv(*this, envconfig(), features);
805 x.createScBridgeObjects(scEnv);
806
807 scEnv(
809 scEnv.close();
810 scEnv(xchain_create_claim_id(x.scBob, x.jvb, x.reward, x.mcBob));
811 scEnv.close();
812
813 auto scEnvAcctObjs = [&](Account const& acct, char const* type) {
814 Json::Value params;
815 params[jss::account] = acct.human();
816 params[jss::type] = type;
817 params[jss::ledger_index] = "validated";
818 return scEnv.rpc("json", "account_objects", to_string(params));
819 };
820
821 {
822 // Find the xchain sequence number for Andrea.
823 Json::Value const resp =
824 scEnvAcctObjs(x.scAlice, jss::xchain_owned_claim_id);
825 BEAST_EXPECT(acctObjsIsSize(resp, 1));
826
827 auto const& xchain_seq =
828 resp[jss::result][jss::account_objects][0u];
829 BEAST_EXPECT(
830 xchain_seq[sfAccount.jsonName] == x.scAlice.human());
831 BEAST_EXPECT(
832 xchain_seq[sfXChainClaimID.getJsonName()].asUInt() == 1);
833 }
834 {
835 // and the one for Bob
836 Json::Value const resp =
837 scEnvAcctObjs(x.scBob, jss::xchain_owned_claim_id);
838 BEAST_EXPECT(acctObjsIsSize(resp, 1));
839
840 auto const& xchain_seq =
841 resp[jss::result][jss::account_objects][0u];
842 BEAST_EXPECT(xchain_seq[sfAccount.jsonName] == x.scBob.human());
843 BEAST_EXPECT(
844 xchain_seq[sfXChainClaimID.getJsonName()].asUInt() == 2);
845 }
846 }
847 {
849 Env scEnv(*this, envconfig(), features);
850 x.createScBridgeObjects(scEnv);
851 auto const amt = XRP(1000);
852
853 // send first batch of account create attestations, so the
854 // xchain_create_account_claim_id should be present on the door
855 // account (Account::master) to collect the signatures until a
856 // quorum is reached
858 x.scAttester,
859 x.jvb,
860 x.mcCarol,
861 amt,
862 x.reward,
863 x.payees[0],
864 true,
865 1,
866 x.scuAlice,
867 x.signers[0]));
868 scEnv.close();
869
870 auto scEnvAcctObjs = [&](Account const& acct, char const* type) {
871 Json::Value params;
872 params[jss::account] = acct.human();
873 params[jss::type] = type;
874 params[jss::ledger_index] = "validated";
875 return scEnv.rpc("json", "account_objects", to_string(params));
876 };
877
878 {
879 // Find the xchain_create_account_claim_id
880 Json::Value const resp = scEnvAcctObjs(
881 Account::master, jss::xchain_owned_create_account_claim_id);
882 BEAST_EXPECT(acctObjsIsSize(resp, 1));
883
884 auto const& xchain_create_account_claim_id =
885 resp[jss::result][jss::account_objects][0u];
886 BEAST_EXPECT(
887 xchain_create_account_claim_id[sfAccount.jsonName] ==
889 BEAST_EXPECT(
890 xchain_create_account_claim_id[sfXChainAccountCreateCount
891 .getJsonName()]
892 .asUInt() == 1);
893 }
894 }
895
896 // gw creates an offer that we can look for in the ledger.
897 env(offer(gw, USD(7), XRP(14)));
898 env.close();
899 {
900 // Find the offer.
901 Json::Value const resp = acctObjs(gw, jss::offer);
902 BEAST_EXPECT(acctObjsIsSize(resp, 1));
903
904 auto const& offer = resp[jss::result][jss::account_objects][0u];
905 BEAST_EXPECT(offer[sfAccount.jsonName] == gw.human());
906 BEAST_EXPECT(offer[sfTakerGets.jsonName].asUInt() == 14'000'000);
907 BEAST_EXPECT(offer[sfTakerPays.jsonName][jss::value].asUInt() == 7);
908 }
909 {
910 // Create a payment channel from qw to alice that we can look
911 // for.
912 Json::Value jvPayChan;
913 jvPayChan[jss::TransactionType] = jss::PaymentChannelCreate;
914 jvPayChan[jss::Account] = gw.human();
915 jvPayChan[jss::Destination] = alice.human();
916 jvPayChan[jss::Amount] =
917 XRP(300).value().getJson(JsonOptions::none);
918 jvPayChan[sfSettleDelay.jsonName] = 24 * 60 * 60;
919 jvPayChan[sfPublicKey.jsonName] = strHex(gw.pk().slice());
920 env(jvPayChan);
921 env.close();
922 }
923 {
924 // Find the payment channel.
925 Json::Value const resp = acctObjs(gw, jss::payment_channel);
926 BEAST_EXPECT(acctObjsIsSize(resp, 1));
927
928 auto const& payChan = resp[jss::result][jss::account_objects][0u];
929 BEAST_EXPECT(payChan[sfAccount.jsonName] == gw.human());
930 BEAST_EXPECT(payChan[sfAmount.jsonName].asUInt() == 300'000'000);
931 BEAST_EXPECT(
932 payChan[sfSettleDelay.jsonName].asUInt() == 24 * 60 * 60);
933 }
934
935 {
936 // gw creates a DID that we can look for in the ledger.
937 Json::Value jvDID;
938 jvDID[jss::TransactionType] = jss::DIDSet;
939 jvDID[jss::Account] = gw.human();
940 jvDID[sfURI.jsonName] = strHex(std::string{"uri"});
941 env(jvDID);
942 env.close();
943 }
944 {
945 // Find the DID.
946 Json::Value const resp = acctObjs(gw, jss::did);
947 BEAST_EXPECT(acctObjsIsSize(resp, 1));
948
949 auto const& did = resp[jss::result][jss::account_objects][0u];
950 BEAST_EXPECT(did[sfAccount.jsonName] == gw.human());
951 BEAST_EXPECT(did[sfURI.jsonName] == strHex(std::string{"uri"}));
952 }
953 // Make gw multisigning by adding a signerList.
954 env(jtx::signers(gw, 6, {{alice, 7}}));
955 env.close();
956 {
957 // Find the signer list.
958 Json::Value const resp = acctObjs(gw, jss::signer_list);
959 BEAST_EXPECT(acctObjsIsSize(resp, 1));
960
961 auto const& signerList =
962 resp[jss::result][jss::account_objects][0u];
963 BEAST_EXPECT(signerList[sfSignerQuorum.jsonName] == 6);
964 auto const& entry = signerList[sfSignerEntries.jsonName][0u]
965 [sfSignerEntry.jsonName];
966 BEAST_EXPECT(entry[sfAccount.jsonName] == alice.human());
967 BEAST_EXPECT(entry[sfSignerWeight.jsonName].asUInt() == 7);
968 }
969
970 {
971 auto const seq = env.seq(gw);
972 // Create a Ticket for gw.
973 env(ticket::create(gw, 1));
974 env.close();
975
976 // Find the ticket.
977 Json::Value const resp = acctObjs(gw, jss::ticket);
978 BEAST_EXPECT(acctObjsIsSize(resp, 1));
979
980 auto const& ticket = resp[jss::result][jss::account_objects][0u];
981 BEAST_EXPECT(ticket[sfAccount.jsonName] == gw.human());
982 BEAST_EXPECT(ticket[sfLedgerEntryType.jsonName] == jss::Ticket);
983 BEAST_EXPECT(ticket[sfTicketSequence.jsonName].asUInt() == seq + 1);
984 }
985
986 {
987 // See how "deletion_blockers_only" handles gw's directory.
988 Json::Value params;
989 params[jss::account] = gw.human();
990 params[jss::deletion_blockers_only] = true;
991 auto resp = env.rpc("json", "account_objects", to_string(params));
992
993 std::vector<std::string> const expectedLedgerTypes = [] {
995 jss::Escrow.c_str(),
996 jss::Check.c_str(),
997 jss::NFTokenPage.c_str(),
998 jss::RippleState.c_str(),
999 jss::PayChannel.c_str(),
1000 jss::PermissionedDomain.c_str()};
1001 std::sort(v.begin(), v.end());
1002 return v;
1003 }();
1004
1005 std::uint32_t const expectedAccountObjects{
1006 static_cast<std::uint32_t>(std::size(expectedLedgerTypes))};
1007
1008 if (BEAST_EXPECT(acctObjsIsSize(resp, expectedAccountObjects)))
1009 {
1010 auto const& aobjs = resp[jss::result][jss::account_objects];
1011 std::vector<std::string> gotLedgerTypes;
1012 gotLedgerTypes.reserve(expectedAccountObjects);
1013 for (std::uint32_t i = 0; i < expectedAccountObjects; ++i)
1014 {
1015 gotLedgerTypes.push_back(
1016 aobjs[i]["LedgerEntryType"].asString());
1017 }
1018 std::sort(gotLedgerTypes.begin(), gotLedgerTypes.end());
1019 BEAST_EXPECT(gotLedgerTypes == expectedLedgerTypes);
1020 }
1021 }
1022 {
1023 // See how "deletion_blockers_only" with `type` handles gw's
1024 // directory.
1025 Json::Value params;
1026 params[jss::account] = gw.human();
1027 params[jss::deletion_blockers_only] = true;
1028 params[jss::type] = jss::escrow;
1029 auto resp = env.rpc("json", "account_objects", to_string(params));
1030
1031 if (BEAST_EXPECT(acctObjsIsSize(resp, 1u)))
1032 {
1033 auto const& aobjs = resp[jss::result][jss::account_objects];
1034 BEAST_EXPECT(aobjs[0u]["LedgerEntryType"] == jss::Escrow);
1035 }
1036 }
1037 {
1038 // Make a lambda to get the types
1039 auto getTypes = [&](Json::Value const& resp,
1040 std::vector<std::string>& typesOut) {
1041 auto const objs = resp[jss::result][jss::account_objects];
1042 for (auto const& obj : resp[jss::result][jss::account_objects])
1043 typesOut.push_back(
1044 obj[sfLedgerEntryType.fieldName].asString());
1045 std::sort(typesOut.begin(), typesOut.end());
1046 };
1047 // Make a lambda we can use to check the number of fetched
1048 // account objects and their ledger type
1049 auto expectObjects =
1050 [&](Json::Value const& resp,
1051 std::vector<std::string> const& types) -> bool {
1052 if (!acctObjsIsSize(resp, types.size()))
1053 return false;
1054 std::vector<std::string> typesOut;
1055 getTypes(resp, typesOut);
1056 return types == typesOut;
1057 };
1058 // Find AMM objects
1059 AMM amm(env, gw, XRP(1'000), USD(1'000));
1060 amm.deposit(alice, USD(1));
1061 // AMM account has 4 objects: AMM object and 3 trustlines
1062 auto const lines = getAccountLines(env, amm.ammAccount());
1063 BEAST_EXPECT(lines[jss::lines].size() == 3);
1064 // request AMM only, doesn't depend on the limit
1065 BEAST_EXPECT(
1066 acctObjsIsSize(acctObjs(amm.ammAccount(), jss::amm), 1));
1067 // request first two objects
1068 auto resp = acctObjs(amm.ammAccount(), std::nullopt, 2);
1069 std::vector<std::string> typesOut;
1070 getTypes(resp, typesOut);
1071 // request next two objects
1072 resp = acctObjs(
1073 amm.ammAccount(),
1074 std::nullopt,
1075 10,
1076 resp[jss::result][jss::marker].asString());
1077 getTypes(resp, typesOut);
1078 BEAST_EXPECT(
1079 (typesOut ==
1081 jss::AMM.c_str(),
1082 jss::RippleState.c_str(),
1083 jss::RippleState.c_str(),
1084 jss::RippleState.c_str()}));
1085 // filter by state: there are three trustlines
1086 resp = acctObjs(amm.ammAccount(), jss::state, 10);
1087 BEAST_EXPECT(expectObjects(
1088 resp,
1089 {jss::RippleState.c_str(),
1090 jss::RippleState.c_str(),
1091 jss::RippleState.c_str()}));
1092 // AMM account doesn't own offers
1093 BEAST_EXPECT(
1094 acctObjsIsSize(acctObjs(amm.ammAccount(), jss::offer), 0));
1095 // gw account doesn't own AMM object
1096 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::amm), 0));
1097 }
1098
1099 // we still expect invalid field type reported for the following types
1100 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::amendments)));
1101 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::directory)));
1102 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::fee)));
1103 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::hashes)));
1104 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::NegativeUNL)));
1105
1106 // Run up the number of directory entries so gw has two
1107 // directory nodes.
1108 for (int d = 1'000'032; d >= 1'000'000; --d)
1109 {
1110 env(offer(gw, USD(1), drops(d)));
1111 env.close();
1112 }
1113
1114 // Verify that the non-returning types still don't return anything.
1115 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::account), 0));
1116 }
1117
1118 void
1120 {
1121 // there's some bug found in account_nfts method that it did not
1122 // return invalid params when providing unassociated nft marker.
1123 // this test tests both situations when providing valid nft marker
1124 // and unassociated nft marker.
1125 testcase("NFTsMarker");
1126
1127 using namespace jtx;
1128 Env env(*this);
1129
1130 Account const bob{"bob"};
1131 env.fund(XRP(10000), bob);
1132
1133 static constexpr unsigned nftsSize = 10;
1134 for (unsigned i = 0; i < nftsSize; i++)
1135 {
1136 env(token::mint(bob, 0));
1137 }
1138
1139 env.close();
1140
1141 // save the NFTokenIDs to use later
1142 std::vector<Json::Value> tokenIDs;
1143 {
1144 Json::Value params;
1145 params[jss::account] = bob.human();
1146 params[jss::ledger_index] = "validated";
1147 Json::Value const resp =
1148 env.rpc("json", "account_nfts", to_string(params));
1149 Json::Value const& nfts = resp[jss::result][jss::account_nfts];
1150 for (Json::Value const& nft : nfts)
1151 tokenIDs.push_back(nft["NFTokenID"]);
1152 }
1153
1154 // this lambda function is used to check if the account_nfts method
1155 // returns the correct token information. lastIndex is used to query the
1156 // last marker.
1157 auto compareNFTs = [&tokenIDs, &env, &bob](
1158 unsigned const limit, unsigned const lastIndex) {
1159 Json::Value params;
1160 params[jss::account] = bob.human();
1161 params[jss::limit] = limit;
1162 params[jss::marker] = tokenIDs[lastIndex];
1163 params[jss::ledger_index] = "validated";
1164 Json::Value const resp =
1165 env.rpc("json", "account_nfts", to_string(params));
1166
1167 if (resp[jss::result].isMember(jss::error))
1168 return false;
1169
1170 Json::Value const& nfts = resp[jss::result][jss::account_nfts];
1171 unsigned const nftsCount = tokenIDs.size() - lastIndex - 1 < limit
1172 ? tokenIDs.size() - lastIndex - 1
1173 : limit;
1174
1175 if (nfts.size() != nftsCount)
1176 return false;
1177
1178 for (unsigned i = 0; i < nftsCount; i++)
1179 {
1180 if (nfts[i]["NFTokenID"] != tokenIDs[lastIndex + 1 + i])
1181 return false;
1182 }
1183
1184 return true;
1185 };
1186
1187 // test a valid marker which is equal to the third tokenID
1188 BEAST_EXPECT(compareNFTs(4, 2));
1189
1190 // test a valid marker which is equal to the 8th tokenID
1191 BEAST_EXPECT(compareNFTs(4, 7));
1192
1193 // lambda that holds common code for invalid cases.
1194 auto testInvalidMarker = [&env, &bob](
1195 auto marker, char const* errorMessage) {
1196 Json::Value params;
1197 params[jss::account] = bob.human();
1198 params[jss::limit] = 4;
1199 params[jss::ledger_index] = jss::validated;
1200 params[jss::marker] = marker;
1201 Json::Value const resp =
1202 env.rpc("json", "account_nfts", to_string(params));
1203 return resp[jss::result][jss::error_message] == errorMessage;
1204 };
1205
1206 // test an invalid marker that is not a string
1207 BEAST_EXPECT(
1208 testInvalidMarker(17, "Invalid field \'marker\', not string."));
1209
1210 // test an invalid marker that has a non-hex character
1211 BEAST_EXPECT(testInvalidMarker(
1212 "00000000F51DFC2A09D62CBBA1DFBDD4691DAC96AD98B900000000000000000G",
1213 "Invalid field \'marker\'."));
1214
1215 // this lambda function is used to create some fake marker using given
1216 // taxon and sequence because we want to test some unassociated markers
1217 // later
1218 auto createFakeNFTMarker = [](AccountID const& issuer,
1219 std::uint32_t taxon,
1220 std::uint32_t tokenSeq,
1221 std::uint16_t flags = 0,
1222 std::uint16_t fee = 0) {
1223 // the marker has the exact same format as an NFTokenID
1225 flags, fee, issuer, nft::toTaxon(taxon), tokenSeq));
1226 };
1227
1228 // test an unassociated marker which does not exist in the NFTokenIDs
1229 BEAST_EXPECT(testInvalidMarker(
1230 createFakeNFTMarker(bob.id(), 0x000000000, 0x00000000),
1231 "Invalid field \'marker\'."));
1232
1233 // test an unassociated marker which exceeds the maximum value of the
1234 // existing NFTokenID
1235 BEAST_EXPECT(testInvalidMarker(
1236 createFakeNFTMarker(bob.id(), 0xFFFFFFFF, 0xFFFFFFFF),
1237 "Invalid field \'marker\'."));
1238 }
1239
1240 void
1242 {
1243 testcase("account_nfts");
1244
1245 using namespace jtx;
1246 Env env(*this);
1247
1248 // test validation
1249 {
1250 auto testInvalidAccountParam = [&](auto const& param) {
1251 Json::Value params;
1252 params[jss::account] = param;
1253 auto jrr = env.rpc(
1254 "json", "account_nfts", to_string(params))[jss::result];
1255 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
1256 BEAST_EXPECT(
1257 jrr[jss::error_message] == "Invalid field 'account'.");
1258 };
1259
1260 testInvalidAccountParam(1);
1261 testInvalidAccountParam(1.1);
1262 testInvalidAccountParam(true);
1263 testInvalidAccountParam(Json::Value(Json::nullValue));
1264 testInvalidAccountParam(Json::Value(Json::objectValue));
1265 testInvalidAccountParam(Json::Value(Json::arrayValue));
1266 }
1267 }
1268
1269 void
1271 {
1272 testcase("AccountObjectMarker");
1273
1274 using namespace jtx;
1275 Env env(*this);
1276
1277 Account const alice{"alice"};
1278 Account const bob{"bob"};
1279 Account const carol{"carol"};
1280 env.fund(XRP(10000), alice, bob, carol);
1281
1282 unsigned const accountObjectSize = 30;
1283 for (unsigned i = 0; i < accountObjectSize; i++)
1284 env(check::create(alice, bob, XRP(10)));
1285
1286 for (unsigned i = 0; i < 10; i++)
1287 env(token::mint(carol, 0));
1288
1289 env.close();
1290
1291 unsigned const limit = 11;
1292 Json::Value marker;
1293
1294 // test account_objects with a limit and update marker
1295 {
1296 Json::Value params;
1297 params[jss::account] = bob.human();
1298 params[jss::limit] = limit;
1299 params[jss::ledger_index] = "validated";
1300 auto resp = env.rpc("json", "account_objects", to_string(params));
1301 auto& accountObjects = resp[jss::result][jss::account_objects];
1302 marker = resp[jss::result][jss::marker];
1303 BEAST_EXPECT(!resp[jss::result].isMember(jss::error));
1304 BEAST_EXPECT(accountObjects.size() == limit);
1305 }
1306
1307 // test account_objects with valid marker and update marker
1308 {
1309 Json::Value params;
1310 params[jss::account] = bob.human();
1311 params[jss::limit] = limit;
1312 params[jss::marker] = marker;
1313 params[jss::ledger_index] = "validated";
1314 auto resp = env.rpc("json", "account_objects", to_string(params));
1315 auto& accountObjects = resp[jss::result][jss::account_objects];
1316 marker = resp[jss::result][jss::marker];
1317 BEAST_EXPECT(!resp[jss::result].isMember(jss::error));
1318 BEAST_EXPECT(accountObjects.size() == limit);
1319 }
1320
1321 // this lambda function is used to check invalid marker response.
1322 auto testInvalidMarker = [&](std::string& marker) {
1323 Json::Value params;
1324 params[jss::account] = bob.human();
1325 params[jss::limit] = limit;
1326 params[jss::ledger_index] = jss::validated;
1327 params[jss::marker] = marker;
1328 Json::Value const resp =
1329 env.rpc("json", "account_objects", to_string(params));
1330 return resp[jss::result][jss::error_message] ==
1331 "Invalid field \'marker\'.";
1332 };
1333
1334 auto const markerStr = marker.asString();
1335 auto const& idx = markerStr.find(',');
1336 auto const dirIndex = markerStr.substr(0, idx);
1337 auto const entryIndex = markerStr.substr(idx + 1);
1338
1339 // test account_objects with an invalid marker that contains no ','
1340 {
1341 std::string s = dirIndex + entryIndex;
1342 BEAST_EXPECT(testInvalidMarker(s));
1343 }
1344
1345 // test invalid marker by adding invalid string after the maker:
1346 // "dirIndex,entryIndex,1234"
1347 {
1348 std::string s = markerStr + ",1234";
1349 BEAST_EXPECT(testInvalidMarker(s));
1350 }
1351
1352 // test account_objects with an invalid marker containing invalid
1353 // dirIndex by replacing some characters from the dirIndex.
1354 {
1355 std::string s = markerStr;
1356 s.replace(0, 7, "FFFFFFF");
1357 BEAST_EXPECT(testInvalidMarker(s));
1358 }
1359
1360 // test account_objects with an invalid marker containing invalid
1361 // entryIndex by replacing some characters from the entryIndex.
1362 {
1363 std::string s = entryIndex;
1364 s.replace(0, 7, "FFFFFFF");
1365 s = dirIndex + ',' + s;
1366 BEAST_EXPECT(testInvalidMarker(s));
1367 }
1368
1369 // test account_objects with an invalid marker containing invalid
1370 // dirIndex with marker: ",entryIndex"
1371 {
1372 std::string s = ',' + entryIndex;
1373 BEAST_EXPECT(testInvalidMarker(s));
1374 }
1375
1376 // test account_objects with marker: "0,entryIndex", this is still
1377 // valid, because when dirIndex = 0, we will use root key to find
1378 // dir.
1379 {
1380 std::string s = "0," + entryIndex;
1381 Json::Value params;
1382 params[jss::account] = bob.human();
1383 params[jss::limit] = limit;
1384 params[jss::marker] = s;
1385 params[jss::ledger_index] = "validated";
1386 auto resp = env.rpc("json", "account_objects", to_string(params));
1387 auto& accountObjects = resp[jss::result][jss::account_objects];
1388 BEAST_EXPECT(!resp[jss::result].isMember(jss::error));
1389 BEAST_EXPECT(accountObjects.size() == limit);
1390 }
1391
1392 // test account_objects with an invalid marker containing invalid
1393 // entryIndex with marker: "dirIndex,"
1394 {
1395 std::string s = dirIndex + ',';
1396 BEAST_EXPECT(testInvalidMarker(s));
1397 }
1398
1399 // test account_objects with an invalid marker containing invalid
1400 // entryIndex with marker: "dirIndex,0"
1401 {
1402 std::string s = dirIndex + ",0";
1403 BEAST_EXPECT(testInvalidMarker(s));
1404 }
1405
1406 // continue getting account_objects with valid marker. This will be the
1407 // last page, so response will not contain any marker.
1408 {
1409 Json::Value params;
1410 params[jss::account] = bob.human();
1411 params[jss::limit] = limit;
1412 params[jss::marker] = marker;
1413 params[jss::ledger_index] = "validated";
1414 auto resp = env.rpc("json", "account_objects", to_string(params));
1415 auto& accountObjects = resp[jss::result][jss::account_objects];
1416 BEAST_EXPECT(!resp[jss::result].isMember(jss::error));
1417 BEAST_EXPECT(
1418 accountObjects.size() == accountObjectSize - limit * 2);
1419 BEAST_EXPECT(!resp[jss::result].isMember(jss::marker));
1420 }
1421
1422 // test account_objects when the account only have nft pages, but
1423 // provided invalid entry index.
1424 {
1425 Json::Value params;
1426 params[jss::account] = carol.human();
1427 params[jss::limit] = 10;
1428 params[jss::marker] = "0," + entryIndex;
1429 params[jss::ledger_index] = "validated";
1430 auto resp = env.rpc("json", "account_objects", to_string(params));
1431 auto& accountObjects = resp[jss::result][jss::account_objects];
1432 BEAST_EXPECT(accountObjects.size() == 0);
1433 }
1434 }
1435
1436 void
1437 run() override
1438 {
1439 testErrors();
1446 }
1447};
1448
1449BEAST_DEFINE_TESTSUITE(AccountObjects, rpc, ripple);
1450
1451} // namespace test
1452} // namespace ripple
T begin(T... args)
Unserialize a JSON document into a Value.
Definition: json_reader.h:39
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Definition: json_reader.cpp:78
Represents a JSON value.
Definition: json_value.h:149
bool isArray() const
UInt size() const
Number of values in array or object.
Definition: json_value.cpp:719
UInt asUInt() const
Definition: json_value.cpp:558
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:482
bool isMember(char const *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:962
A testsuite class.
Definition: suite.h:55
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:155
static uint256 createNFTokenID(std::uint16_t flags, std::uint16_t fee, AccountID const &issuer, nft::Taxon taxon, std::uint32_t tokenSeq)
void run() override
Runs the suite.
Convenience class to test AMM functionality.
Definition: AMM.h:124
Immutable cryptographic account descriptor.
Definition: Account.h:39
static Account const master
The master account.
Definition: Account.h:48
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:114
A transaction testing environment.
Definition: Env.h:121
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition: Env.cpp:254
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:117
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:306
NetClock::time_point now()
Returns the current network time.
Definition: Env.h:284
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:788
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:275
Set the fee on a JTx.
Definition: fee.h:37
Match set account flags.
Definition: flags.h:128
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition: rpc.h:35
Set the flags on a JTx.
Definition: txflags.h:31
T end(T... args)
T find(T... args)
@ nullValue
'null' value
Definition: json_value.h:38
@ arrayValue
array value (ordered list)
Definition: json_value.h:44
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:45
Taxon toTaxon(std::uint32_t i)
Definition: nft.h:42
Json::Value create(A const &account, A const &dest, STAmount const &sendMax)
Create a check.
Definition: TestHelpers.h:329
Json::Value auth(Account const &account, Account const &auth)
Preauthorize for deposit.
Definition: deposit.cpp:32
Json::Value setTx(AccountID const &account, Credentials const &credentials, std::optional< uint256 > domain)
Json::Value create(Account const &account, std::uint32_t count)
Create one of more tickets.
Definition: ticket.cpp:31
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:68
Json::Value mint(jtx::Account const &account, std::uint32_t nfTokenTaxon)
Mint an NFToken.
Definition: token.cpp:34
Json::Value create_account_attestation(jtx::Account const &submittingAccount, Json::Value const &jvBridge, jtx::Account const &sendingAccount, jtx::AnyAmount const &sendingAmount, jtx::AnyAmount const &rewardAmount, jtx::Account const &rewardAccount, bool wasLockingChainSend, std::uint64_t createCount, jtx::Account const &dst, jtx::signer const &signer)
Json::Value signers(Account const &account, std::uint32_t quorum, std::vector< signer > const &v)
Definition: multisign.cpp:34
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value getAccountLines(Env &env, AccountID const &acctId)
Definition: TestHelpers.cpp:40
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:30
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:54
FeatureBitset testable_amendments()
Definition: Env.h:74
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:29
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
static char const * bobs_account_objects[]
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:25
constexpr std::uint32_t tfPassive
Definition: TxFlags.h:98
std::string strHex(FwdIt begin, FwdIt end)
Definition: strHex.h:30
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
@ credential
Credentials signature.
constexpr std::uint32_t const tfTransferable
Definition: TxFlags.h:140
T push_back(T... args)
T replace(T... args)
T reserve(T... args)
T size(T... args)
T sort(T... args)
std::vector< Account > const payees
std::vector< signer > const signers
Set the sequence number on a JTx.
Definition: seq.h:34
T time_since_epoch(T... args)