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