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