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