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 = supported_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::Flags] = tfUniversal;
702 jvEscrow[jss::Account] = gw.human();
703 jvEscrow[jss::Destination] = gw.human();
704 jvEscrow[jss::Amount] = XRP(100).value().getJson(JsonOptions::none);
705 jvEscrow[sfFinishAfter.jsonName] =
706 env.now().time_since_epoch().count() + 1;
707 env(jvEscrow);
708 env.close();
709 }
710 {
711 // Find the escrow.
712 Json::Value const resp = acctObjs(gw, jss::escrow);
713 BEAST_EXPECT(acctObjsIsSize(resp, 1));
714
715 auto const& escrow = resp[jss::result][jss::account_objects][0u];
716 BEAST_EXPECT(escrow[sfAccount.jsonName] == gw.human());
717 BEAST_EXPECT(escrow[sfDestination.jsonName] == gw.human());
718 BEAST_EXPECT(escrow[sfAmount.jsonName].asUInt() == 100'000'000);
719 }
720
721 {
722 std::string const credentialType1 = "credential1";
723 Account issuer("issuer");
724 env.fund(XRP(5000), issuer);
725
726 // gw creates an PermissionedDomain.
727 env(pdomain::setTx(gw, {{issuer, credentialType1}}));
728 env.close();
729
730 // Find the PermissionedDomain.
731 Json::Value const resp = acctObjs(gw, jss::permissioned_domain);
732 BEAST_EXPECT(acctObjsIsSize(resp, 1));
733
734 auto const& permissionedDomain =
735 resp[jss::result][jss::account_objects][0u];
736 BEAST_EXPECT(
737 permissionedDomain.isMember(jss::Owner) &&
738 (permissionedDomain[jss::Owner] == gw.human()));
739 bool const check1 = BEAST_EXPECT(
740 permissionedDomain.isMember(jss::AcceptedCredentials) &&
741 permissionedDomain[jss::AcceptedCredentials].isArray() &&
742 (permissionedDomain[jss::AcceptedCredentials].size() == 1) &&
743 (permissionedDomain[jss::AcceptedCredentials][0u].isMember(
744 jss::Credential)));
745
746 if (check1)
747 {
748 auto const& credential =
749 permissionedDomain[jss::AcceptedCredentials][0u]
750 [jss::Credential];
751 BEAST_EXPECT(
752 credential.isMember(sfIssuer.jsonName) &&
753 (credential[sfIssuer.jsonName] == issuer.human()));
754 BEAST_EXPECT(
755 credential.isMember(sfCredentialType.jsonName) &&
756 (credential[sfCredentialType.jsonName] ==
757 strHex(credentialType1)));
758 }
759 }
760
761 {
762 // Create a bridge
764 Env scEnv(*this, envconfig(), features);
765 x.createScBridgeObjects(scEnv);
766
767 auto scEnvAcctObjs = [&](Account const& acct, char const* type) {
768 Json::Value params;
769 params[jss::account] = acct.human();
770 params[jss::type] = type;
771 params[jss::ledger_index] = "validated";
772 return scEnv.rpc("json", "account_objects", to_string(params));
773 };
774
775 Json::Value const resp =
776 scEnvAcctObjs(Account::master, jss::bridge);
777
778 BEAST_EXPECT(acctObjsIsSize(resp, 1));
779 auto const& acct_bridge =
780 resp[jss::result][jss::account_objects][0u];
781 BEAST_EXPECT(
782 acct_bridge[sfAccount.jsonName] == Account::master.human());
783 BEAST_EXPECT(
784 acct_bridge[sfLedgerEntryType.getJsonName()] == "Bridge");
785 BEAST_EXPECT(
786 acct_bridge[sfXChainClaimID.getJsonName()].asUInt() == 0);
787 BEAST_EXPECT(
788 acct_bridge[sfXChainAccountClaimCount.getJsonName()].asUInt() ==
789 0);
790 BEAST_EXPECT(
791 acct_bridge[sfXChainAccountCreateCount.getJsonName()]
792 .asUInt() == 0);
793 BEAST_EXPECT(
794 acct_bridge[sfMinAccountCreateAmount.getJsonName()].asUInt() ==
795 20000000);
796 BEAST_EXPECT(
797 acct_bridge[sfSignatureReward.getJsonName()].asUInt() ==
798 1000000);
799 BEAST_EXPECT(acct_bridge[sfXChainBridge.getJsonName()] == x.jvb);
800 }
801 {
802 // Alice and Bob create a xchain sequence number that we can look
803 // for in the ledger.
805 Env scEnv(*this, envconfig(), features);
806 x.createScBridgeObjects(scEnv);
807
808 scEnv(
810 scEnv.close();
811 scEnv(xchain_create_claim_id(x.scBob, x.jvb, x.reward, x.mcBob));
812 scEnv.close();
813
814 auto scEnvAcctObjs = [&](Account const& acct, char const* type) {
815 Json::Value params;
816 params[jss::account] = acct.human();
817 params[jss::type] = type;
818 params[jss::ledger_index] = "validated";
819 return scEnv.rpc("json", "account_objects", to_string(params));
820 };
821
822 {
823 // Find the xchain sequence number for Andrea.
824 Json::Value const resp =
825 scEnvAcctObjs(x.scAlice, jss::xchain_owned_claim_id);
826 BEAST_EXPECT(acctObjsIsSize(resp, 1));
827
828 auto const& xchain_seq =
829 resp[jss::result][jss::account_objects][0u];
830 BEAST_EXPECT(
831 xchain_seq[sfAccount.jsonName] == x.scAlice.human());
832 BEAST_EXPECT(
833 xchain_seq[sfXChainClaimID.getJsonName()].asUInt() == 1);
834 }
835 {
836 // and the one for Bob
837 Json::Value const resp =
838 scEnvAcctObjs(x.scBob, jss::xchain_owned_claim_id);
839 BEAST_EXPECT(acctObjsIsSize(resp, 1));
840
841 auto const& xchain_seq =
842 resp[jss::result][jss::account_objects][0u];
843 BEAST_EXPECT(xchain_seq[sfAccount.jsonName] == x.scBob.human());
844 BEAST_EXPECT(
845 xchain_seq[sfXChainClaimID.getJsonName()].asUInt() == 2);
846 }
847 }
848 {
850 Env scEnv(*this, envconfig(), features);
851 x.createScBridgeObjects(scEnv);
852 auto const amt = XRP(1000);
853
854 // send first batch of account create attestations, so the
855 // xchain_create_account_claim_id should be present on the door
856 // account (Account::master) to collect the signatures until a
857 // quorum is reached
859 x.scAttester,
860 x.jvb,
861 x.mcCarol,
862 amt,
863 x.reward,
864 x.payees[0],
865 true,
866 1,
867 x.scuAlice,
868 x.signers[0]));
869 scEnv.close();
870
871 auto scEnvAcctObjs = [&](Account const& acct, char const* type) {
872 Json::Value params;
873 params[jss::account] = acct.human();
874 params[jss::type] = type;
875 params[jss::ledger_index] = "validated";
876 return scEnv.rpc("json", "account_objects", to_string(params));
877 };
878
879 {
880 // Find the xchain_create_account_claim_id
881 Json::Value const resp = scEnvAcctObjs(
882 Account::master, jss::xchain_owned_create_account_claim_id);
883 BEAST_EXPECT(acctObjsIsSize(resp, 1));
884
885 auto const& xchain_create_account_claim_id =
886 resp[jss::result][jss::account_objects][0u];
887 BEAST_EXPECT(
888 xchain_create_account_claim_id[sfAccount.jsonName] ==
890 BEAST_EXPECT(
891 xchain_create_account_claim_id[sfXChainAccountCreateCount
892 .getJsonName()]
893 .asUInt() == 1);
894 }
895 }
896
897 // gw creates an offer that we can look for in the ledger.
898 env(offer(gw, USD(7), XRP(14)));
899 env.close();
900 {
901 // Find the offer.
902 Json::Value const resp = acctObjs(gw, jss::offer);
903 BEAST_EXPECT(acctObjsIsSize(resp, 1));
904
905 auto const& offer = resp[jss::result][jss::account_objects][0u];
906 BEAST_EXPECT(offer[sfAccount.jsonName] == gw.human());
907 BEAST_EXPECT(offer[sfTakerGets.jsonName].asUInt() == 14'000'000);
908 BEAST_EXPECT(offer[sfTakerPays.jsonName][jss::value].asUInt() == 7);
909 }
910 {
911 // Create a payment channel from qw to alice that we can look
912 // for.
913 Json::Value jvPayChan;
914 jvPayChan[jss::TransactionType] = jss::PaymentChannelCreate;
915 jvPayChan[jss::Flags] = tfUniversal;
916 jvPayChan[jss::Account] = gw.human();
917 jvPayChan[jss::Destination] = alice.human();
918 jvPayChan[jss::Amount] =
919 XRP(300).value().getJson(JsonOptions::none);
920 jvPayChan[sfSettleDelay.jsonName] = 24 * 60 * 60;
921 jvPayChan[sfPublicKey.jsonName] = strHex(gw.pk().slice());
922 env(jvPayChan);
923 env.close();
924 }
925 {
926 // Find the payment channel.
927 Json::Value const resp = acctObjs(gw, jss::payment_channel);
928 BEAST_EXPECT(acctObjsIsSize(resp, 1));
929
930 auto const& payChan = resp[jss::result][jss::account_objects][0u];
931 BEAST_EXPECT(payChan[sfAccount.jsonName] == gw.human());
932 BEAST_EXPECT(payChan[sfAmount.jsonName].asUInt() == 300'000'000);
933 BEAST_EXPECT(
934 payChan[sfSettleDelay.jsonName].asUInt() == 24 * 60 * 60);
935 }
936
937 {
938 // gw creates a DID that we can look for in the ledger.
939 Json::Value jvDID;
940 jvDID[jss::TransactionType] = jss::DIDSet;
941 jvDID[jss::Flags] = tfUniversal;
942 jvDID[jss::Account] = gw.human();
943 jvDID[sfURI.jsonName] = strHex(std::string{"uri"});
944 env(jvDID);
945 env.close();
946 }
947 {
948 // Find the DID.
949 Json::Value const resp = acctObjs(gw, jss::did);
950 BEAST_EXPECT(acctObjsIsSize(resp, 1));
951
952 auto const& did = resp[jss::result][jss::account_objects][0u];
953 BEAST_EXPECT(did[sfAccount.jsonName] == gw.human());
954 BEAST_EXPECT(did[sfURI.jsonName] == strHex(std::string{"uri"}));
955 }
956 // Make gw multisigning by adding a signerList.
957 env(jtx::signers(gw, 6, {{alice, 7}}));
958 env.close();
959 {
960 // Find the signer list.
961 Json::Value const resp = acctObjs(gw, jss::signer_list);
962 BEAST_EXPECT(acctObjsIsSize(resp, 1));
963
964 auto const& signerList =
965 resp[jss::result][jss::account_objects][0u];
966 BEAST_EXPECT(signerList[sfSignerQuorum.jsonName] == 6);
967 auto const& entry = signerList[sfSignerEntries.jsonName][0u]
968 [sfSignerEntry.jsonName];
969 BEAST_EXPECT(entry[sfAccount.jsonName] == alice.human());
970 BEAST_EXPECT(entry[sfSignerWeight.jsonName].asUInt() == 7);
971 }
972
973 {
974 auto const seq = env.seq(gw);
975 // Create a Ticket for gw.
976 env(ticket::create(gw, 1));
977 env.close();
978
979 // Find the ticket.
980 Json::Value const resp = acctObjs(gw, jss::ticket);
981 BEAST_EXPECT(acctObjsIsSize(resp, 1));
982
983 auto const& ticket = resp[jss::result][jss::account_objects][0u];
984 BEAST_EXPECT(ticket[sfAccount.jsonName] == gw.human());
985 BEAST_EXPECT(ticket[sfLedgerEntryType.jsonName] == jss::Ticket);
986 BEAST_EXPECT(ticket[sfTicketSequence.jsonName].asUInt() == seq + 1);
987 }
988
989 {
990 // See how "deletion_blockers_only" handles gw's directory.
991 Json::Value params;
992 params[jss::account] = gw.human();
993 params[jss::deletion_blockers_only] = true;
994 auto resp = env.rpc("json", "account_objects", to_string(params));
995
996 std::vector<std::string> const expectedLedgerTypes = [] {
998 jss::Escrow.c_str(),
999 jss::Check.c_str(),
1000 jss::NFTokenPage.c_str(),
1001 jss::RippleState.c_str(),
1002 jss::PayChannel.c_str(),
1003 jss::PermissionedDomain.c_str()};
1004 std::sort(v.begin(), v.end());
1005 return v;
1006 }();
1007
1008 std::uint32_t const expectedAccountObjects{
1009 static_cast<std::uint32_t>(std::size(expectedLedgerTypes))};
1010
1011 if (BEAST_EXPECT(acctObjsIsSize(resp, expectedAccountObjects)))
1012 {
1013 auto const& aobjs = resp[jss::result][jss::account_objects];
1014 std::vector<std::string> gotLedgerTypes;
1015 gotLedgerTypes.reserve(expectedAccountObjects);
1016 for (std::uint32_t i = 0; i < expectedAccountObjects; ++i)
1017 {
1018 gotLedgerTypes.push_back(
1019 aobjs[i]["LedgerEntryType"].asString());
1020 }
1021 std::sort(gotLedgerTypes.begin(), gotLedgerTypes.end());
1022 BEAST_EXPECT(gotLedgerTypes == expectedLedgerTypes);
1023 }
1024 }
1025 {
1026 // See how "deletion_blockers_only" with `type` handles gw's
1027 // directory.
1028 Json::Value params;
1029 params[jss::account] = gw.human();
1030 params[jss::deletion_blockers_only] = true;
1031 params[jss::type] = jss::escrow;
1032 auto resp = env.rpc("json", "account_objects", to_string(params));
1033
1034 if (BEAST_EXPECT(acctObjsIsSize(resp, 1u)))
1035 {
1036 auto const& aobjs = resp[jss::result][jss::account_objects];
1037 BEAST_EXPECT(aobjs[0u]["LedgerEntryType"] == jss::Escrow);
1038 }
1039 }
1040 {
1041 // Make a lambda to get the types
1042 auto getTypes = [&](Json::Value const& resp,
1043 std::vector<std::string>& typesOut) {
1044 auto const objs = resp[jss::result][jss::account_objects];
1045 for (auto const& obj : resp[jss::result][jss::account_objects])
1046 typesOut.push_back(
1047 obj[sfLedgerEntryType.fieldName].asString());
1048 std::sort(typesOut.begin(), typesOut.end());
1049 };
1050 // Make a lambda we can use to check the number of fetched
1051 // account objects and their ledger type
1052 auto expectObjects =
1053 [&](Json::Value const& resp,
1054 std::vector<std::string> const& types) -> bool {
1055 if (!acctObjsIsSize(resp, types.size()))
1056 return false;
1057 std::vector<std::string> typesOut;
1058 getTypes(resp, typesOut);
1059 return types == typesOut;
1060 };
1061 // Find AMM objects
1062 AMM amm(env, gw, XRP(1'000), USD(1'000));
1063 amm.deposit(alice, USD(1));
1064 // AMM account has 4 objects: AMM object and 3 trustlines
1065 auto const lines = getAccountLines(env, amm.ammAccount());
1066 BEAST_EXPECT(lines[jss::lines].size() == 3);
1067 // request AMM only, doesn't depend on the limit
1068 BEAST_EXPECT(
1069 acctObjsIsSize(acctObjs(amm.ammAccount(), jss::amm), 1));
1070 // request first two objects
1071 auto resp = acctObjs(amm.ammAccount(), std::nullopt, 2);
1072 std::vector<std::string> typesOut;
1073 getTypes(resp, typesOut);
1074 // request next two objects
1075 resp = acctObjs(
1076 amm.ammAccount(),
1077 std::nullopt,
1078 10,
1079 resp[jss::result][jss::marker].asString());
1080 getTypes(resp, typesOut);
1081 BEAST_EXPECT(
1082 (typesOut ==
1084 jss::AMM.c_str(),
1085 jss::RippleState.c_str(),
1086 jss::RippleState.c_str(),
1087 jss::RippleState.c_str()}));
1088 // filter by state: there are three trustlines
1089 resp = acctObjs(amm.ammAccount(), jss::state, 10);
1090 BEAST_EXPECT(expectObjects(
1091 resp,
1092 {jss::RippleState.c_str(),
1093 jss::RippleState.c_str(),
1094 jss::RippleState.c_str()}));
1095 // AMM account doesn't own offers
1096 BEAST_EXPECT(
1097 acctObjsIsSize(acctObjs(amm.ammAccount(), jss::offer), 0));
1098 // gw account doesn't own AMM object
1099 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::amm), 0));
1100 }
1101
1102 // we still expect invalid field type reported for the following types
1103 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::amendments)));
1104 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::directory)));
1105 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::fee)));
1106 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::hashes)));
1107 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::NegativeUNL)));
1108
1109 // Run up the number of directory entries so gw has two
1110 // directory nodes.
1111 for (int d = 1'000'032; d >= 1'000'000; --d)
1112 {
1113 env(offer(gw, USD(1), drops(d)));
1114 env.close();
1115 }
1116
1117 // Verify that the non-returning types still don't return anything.
1118 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::account), 0));
1119 }
1120
1121 void
1123 {
1124 // there's some bug found in account_nfts method that it did not
1125 // return invalid params when providing unassociated nft marker.
1126 // this test tests both situations when providing valid nft marker
1127 // and unassociated nft marker.
1128 testcase("NFTsMarker");
1129
1130 using namespace jtx;
1131 Env env(*this);
1132
1133 Account const bob{"bob"};
1134 env.fund(XRP(10000), bob);
1135
1136 static constexpr unsigned nftsSize = 10;
1137 for (unsigned i = 0; i < nftsSize; i++)
1138 {
1139 env(token::mint(bob, 0));
1140 }
1141
1142 env.close();
1143
1144 // save the NFTokenIDs to use later
1145 std::vector<Json::Value> tokenIDs;
1146 {
1147 Json::Value params;
1148 params[jss::account] = bob.human();
1149 params[jss::ledger_index] = "validated";
1150 Json::Value const resp =
1151 env.rpc("json", "account_nfts", to_string(params));
1152 Json::Value const& nfts = resp[jss::result][jss::account_nfts];
1153 for (Json::Value const& nft : nfts)
1154 tokenIDs.push_back(nft["NFTokenID"]);
1155 }
1156
1157 // this lambda function is used to check if the account_nfts method
1158 // returns the correct token information. lastIndex is used to query the
1159 // last marker.
1160 auto compareNFTs = [&tokenIDs, &env, &bob](
1161 unsigned const limit, unsigned const lastIndex) {
1162 Json::Value params;
1163 params[jss::account] = bob.human();
1164 params[jss::limit] = limit;
1165 params[jss::marker] = tokenIDs[lastIndex];
1166 params[jss::ledger_index] = "validated";
1167 Json::Value const resp =
1168 env.rpc("json", "account_nfts", to_string(params));
1169
1170 if (resp[jss::result].isMember(jss::error))
1171 return false;
1172
1173 Json::Value const& nfts = resp[jss::result][jss::account_nfts];
1174 unsigned const nftsCount = tokenIDs.size() - lastIndex - 1 < limit
1175 ? tokenIDs.size() - lastIndex - 1
1176 : limit;
1177
1178 if (nfts.size() != nftsCount)
1179 return false;
1180
1181 for (unsigned i = 0; i < nftsCount; i++)
1182 {
1183 if (nfts[i]["NFTokenID"] != tokenIDs[lastIndex + 1 + i])
1184 return false;
1185 }
1186
1187 return true;
1188 };
1189
1190 // test a valid marker which is equal to the third tokenID
1191 BEAST_EXPECT(compareNFTs(4, 2));
1192
1193 // test a valid marker which is equal to the 8th tokenID
1194 BEAST_EXPECT(compareNFTs(4, 7));
1195
1196 // lambda that holds common code for invalid cases.
1197 auto testInvalidMarker = [&env, &bob](
1198 auto marker, char const* errorMessage) {
1199 Json::Value params;
1200 params[jss::account] = bob.human();
1201 params[jss::limit] = 4;
1202 params[jss::ledger_index] = jss::validated;
1203 params[jss::marker] = marker;
1204 Json::Value const resp =
1205 env.rpc("json", "account_nfts", to_string(params));
1206 return resp[jss::result][jss::error_message] == errorMessage;
1207 };
1208
1209 // test an invalid marker that is not a string
1210 BEAST_EXPECT(
1211 testInvalidMarker(17, "Invalid field \'marker\', not string."));
1212
1213 // test an invalid marker that has a non-hex character
1214 BEAST_EXPECT(testInvalidMarker(
1215 "00000000F51DFC2A09D62CBBA1DFBDD4691DAC96AD98B900000000000000000G",
1216 "Invalid field \'marker\'."));
1217
1218 // this lambda function is used to create some fake marker using given
1219 // taxon and sequence because we want to test some unassociated markers
1220 // later
1221 auto createFakeNFTMarker = [](AccountID const& issuer,
1222 std::uint32_t taxon,
1223 std::uint32_t tokenSeq,
1224 std::uint16_t flags = 0,
1225 std::uint16_t fee = 0) {
1226 // the marker has the exact same format as an NFTokenID
1228 flags, fee, issuer, nft::toTaxon(taxon), tokenSeq));
1229 };
1230
1231 // test an unassociated marker which does not exist in the NFTokenIDs
1232 BEAST_EXPECT(testInvalidMarker(
1233 createFakeNFTMarker(bob.id(), 0x000000000, 0x00000000),
1234 "Invalid field \'marker\'."));
1235
1236 // test an unassociated marker which exceeds the maximum value of the
1237 // existing NFTokenID
1238 BEAST_EXPECT(testInvalidMarker(
1239 createFakeNFTMarker(bob.id(), 0xFFFFFFFF, 0xFFFFFFFF),
1240 "Invalid field \'marker\'."));
1241 }
1242
1243 void
1245 {
1246 testcase("account_nfts");
1247
1248 using namespace jtx;
1249 Env env(*this);
1250
1251 // test validation
1252 {
1253 auto testInvalidAccountParam = [&](auto const& param) {
1254 Json::Value params;
1255 params[jss::account] = param;
1256 auto jrr = env.rpc(
1257 "json", "account_nfts", to_string(params))[jss::result];
1258 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
1259 BEAST_EXPECT(
1260 jrr[jss::error_message] == "Invalid field 'account'.");
1261 };
1262
1263 testInvalidAccountParam(1);
1264 testInvalidAccountParam(1.1);
1265 testInvalidAccountParam(true);
1266 testInvalidAccountParam(Json::Value(Json::nullValue));
1267 testInvalidAccountParam(Json::Value(Json::objectValue));
1268 testInvalidAccountParam(Json::Value(Json::arrayValue));
1269 }
1270 }
1271
1272 void
1274 {
1275 testcase("AccountObjectMarker");
1276
1277 using namespace jtx;
1278 Env env(*this);
1279
1280 Account const alice{"alice"};
1281 Account const bob{"bob"};
1282 Account const carol{"carol"};
1283 env.fund(XRP(10000), alice, bob, carol);
1284
1285 unsigned const accountObjectSize = 30;
1286 for (unsigned i = 0; i < accountObjectSize; i++)
1287 env(check::create(alice, bob, XRP(10)));
1288
1289 for (unsigned i = 0; i < 10; i++)
1290 env(token::mint(carol, 0));
1291
1292 env.close();
1293
1294 unsigned const limit = 11;
1295 Json::Value marker;
1296
1297 // test account_objects with a limit and update marker
1298 {
1299 Json::Value params;
1300 params[jss::account] = bob.human();
1301 params[jss::limit] = limit;
1302 params[jss::ledger_index] = "validated";
1303 auto resp = env.rpc("json", "account_objects", to_string(params));
1304 auto& accountObjects = resp[jss::result][jss::account_objects];
1305 marker = resp[jss::result][jss::marker];
1306 BEAST_EXPECT(!resp[jss::result].isMember(jss::error));
1307 BEAST_EXPECT(accountObjects.size() == limit);
1308 }
1309
1310 // test account_objects with valid marker and update marker
1311 {
1312 Json::Value params;
1313 params[jss::account] = bob.human();
1314 params[jss::limit] = limit;
1315 params[jss::marker] = marker;
1316 params[jss::ledger_index] = "validated";
1317 auto resp = env.rpc("json", "account_objects", to_string(params));
1318 auto& accountObjects = resp[jss::result][jss::account_objects];
1319 marker = resp[jss::result][jss::marker];
1320 BEAST_EXPECT(!resp[jss::result].isMember(jss::error));
1321 BEAST_EXPECT(accountObjects.size() == limit);
1322 }
1323
1324 // this lambda function is used to check invalid marker response.
1325 auto testInvalidMarker = [&](std::string& marker) {
1326 Json::Value params;
1327 params[jss::account] = bob.human();
1328 params[jss::limit] = limit;
1329 params[jss::ledger_index] = jss::validated;
1330 params[jss::marker] = marker;
1331 Json::Value const resp =
1332 env.rpc("json", "account_objects", to_string(params));
1333 return resp[jss::result][jss::error_message] ==
1334 "Invalid field \'marker\'.";
1335 };
1336
1337 auto const markerStr = marker.asString();
1338 auto const& idx = markerStr.find(',');
1339 auto const dirIndex = markerStr.substr(0, idx);
1340 auto const entryIndex = markerStr.substr(idx + 1);
1341
1342 // test account_objects with an invalid marker that contains no ','
1343 {
1344 std::string s = dirIndex + entryIndex;
1345 BEAST_EXPECT(testInvalidMarker(s));
1346 }
1347
1348 // test invalid marker by adding invalid string after the maker:
1349 // "dirIndex,entryIndex,1234"
1350 {
1351 std::string s = markerStr + ",1234";
1352 BEAST_EXPECT(testInvalidMarker(s));
1353 }
1354
1355 // test account_objects with an invalid marker containing invalid
1356 // dirIndex by replacing some characters from the dirIndex.
1357 {
1358 std::string s = markerStr;
1359 s.replace(0, 7, "FFFFFFF");
1360 BEAST_EXPECT(testInvalidMarker(s));
1361 }
1362
1363 // test account_objects with an invalid marker containing invalid
1364 // entryIndex by replacing some characters from the entryIndex.
1365 {
1366 std::string s = entryIndex;
1367 s.replace(0, 7, "FFFFFFF");
1368 s = dirIndex + ',' + s;
1369 BEAST_EXPECT(testInvalidMarker(s));
1370 }
1371
1372 // test account_objects with an invalid marker containing invalid
1373 // dirIndex with marker: ",entryIndex"
1374 {
1375 std::string s = ',' + entryIndex;
1376 BEAST_EXPECT(testInvalidMarker(s));
1377 }
1378
1379 // test account_objects with marker: "0,entryIndex", this is still
1380 // valid, because when dirIndex = 0, we will use root key to find
1381 // dir.
1382 {
1383 std::string s = "0," + entryIndex;
1384 Json::Value params;
1385 params[jss::account] = bob.human();
1386 params[jss::limit] = limit;
1387 params[jss::marker] = s;
1388 params[jss::ledger_index] = "validated";
1389 auto resp = env.rpc("json", "account_objects", to_string(params));
1390 auto& accountObjects = resp[jss::result][jss::account_objects];
1391 BEAST_EXPECT(!resp[jss::result].isMember(jss::error));
1392 BEAST_EXPECT(accountObjects.size() == limit);
1393 }
1394
1395 // test account_objects with an invalid marker containing invalid
1396 // entryIndex with marker: "dirIndex,"
1397 {
1398 std::string s = dirIndex + ',';
1399 BEAST_EXPECT(testInvalidMarker(s));
1400 }
1401
1402 // test account_objects with an invalid marker containing invalid
1403 // entryIndex with marker: "dirIndex,0"
1404 {
1405 std::string s = dirIndex + ",0";
1406 BEAST_EXPECT(testInvalidMarker(s));
1407 }
1408
1409 // continue getting account_objects with valid marker. This will be the
1410 // last page, so response will not contain any marker.
1411 {
1412 Json::Value params;
1413 params[jss::account] = bob.human();
1414 params[jss::limit] = limit;
1415 params[jss::marker] = marker;
1416 params[jss::ledger_index] = "validated";
1417 auto resp = env.rpc("json", "account_objects", to_string(params));
1418 auto& accountObjects = resp[jss::result][jss::account_objects];
1419 BEAST_EXPECT(!resp[jss::result].isMember(jss::error));
1420 BEAST_EXPECT(
1421 accountObjects.size() == accountObjectSize - limit * 2);
1422 BEAST_EXPECT(!resp[jss::result].isMember(jss::marker));
1423 }
1424
1425 // test account_objects when the account only have nft pages, but
1426 // provided invalid entry index.
1427 {
1428 Json::Value params;
1429 params[jss::account] = carol.human();
1430 params[jss::limit] = 10;
1431 params[jss::marker] = "0," + entryIndex;
1432 params[jss::ledger_index] = "validated";
1433 auto resp = env.rpc("json", "account_objects", to_string(params));
1434 auto& accountObjects = resp[jss::result][jss::account_objects];
1435 BEAST_EXPECT(accountObjects.size() == 0);
1436 }
1437 }
1438
1439 void
1440 run() override
1441 {
1442 testErrors();
1449 }
1450};
1451
1452BEAST_DEFINE_TESTSUITE(AccountObjects, rpc, ripple);
1453
1454} // namespace test
1455} // 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:148
bool isArray() const
UInt size() const
Number of values in array or object.
Definition: json_value.cpp:712
UInt asUInt() const
Definition: json_value.cpp:551
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:475
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:949
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:120
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition: Env.cpp:212
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:264
NetClock::time_point now()
Returns the current network time.
Definition: Env.h:283
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:769
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
Set the fee on a JTx.
Definition: fee.h:37
Match set account flags.
Definition: flags.h:125
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:37
@ arrayValue
array value (ordered list)
Definition: json_value.h:43
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:44
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:441
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 escrow(AccountID const &account, AccountID const &to, STAmount const &amount)
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
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
FeatureBitset supported_amendments()
Definition: Env.h:73
static char const * bobs_account_objects[]
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
constexpr std::uint32_t tfPassive
Definition: TxFlags.h:96
std::string strHex(FwdIt begin, FwdIt end)
Definition: strHex.h:30
constexpr std::uint32_t tfUniversal
Definition: TxFlags.h:61
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:143
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)