rippled
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 <ripple/json/json_reader.h>
21 #include <ripple/json/json_value.h>
22 #include <ripple/json/to_string.h>
23 #include <ripple/protocol/jss.h>
24 #include <test/jtx.h>
25 #include <test/jtx/AMM.h>
26 
27 #include <boost/utility/string_ref.hpp>
28 
29 #include <algorithm>
30 
31 namespace ripple {
32 namespace test {
33 
34 static char const* bobs_account_objects[] = {
35  R"json({
36  "Account" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
37  "BookDirectory" : "50AD0A9E54D2B381288D535EB724E4275FFBF41580D28A925D038D7EA4C68000",
38  "BookNode" : "0",
39  "Flags" : 65536,
40  "LedgerEntryType" : "Offer",
41  "OwnerNode" : "0",
42  "Sequence" : 6,
43  "TakerGets" : {
44  "currency" : "USD",
45  "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
46  "value" : "1"
47  },
48  "TakerPays" : "100000000",
49  "index" : "29665262716C19830E26AEEC0916E476FC7D8EF195FF3B4F06829E64F82A3B3E"
50 })json",
51  R"json({
52  "Balance" : {
53  "currency" : "USD",
54  "issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji",
55  "value" : "-1000"
56  },
57  "Flags" : 131072,
58  "HighLimit" : {
59  "currency" : "USD",
60  "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
61  "value" : "1000"
62  },
63  "HighNode" : "0",
64  "LedgerEntryType" : "RippleState",
65  "LowLimit" : {
66  "currency" : "USD",
67  "issuer" : "r9cZvwKU3zzuZK9JFovGg1JC5n7QiqNL8L",
68  "value" : "0"
69  },
70  "LowNode" : "0",
71  "index" : "D13183BCFFC9AAC9F96AEBB5F66E4A652AD1F5D10273AEB615478302BEBFD4A4"
72 })json",
73  R"json({
74  "Balance" : {
75  "currency" : "USD",
76  "issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji",
77  "value" : "-1000"
78  },
79  "Flags" : 131072,
80  "HighLimit" : {
81  "currency" : "USD",
82  "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
83  "value" : "1000"
84  },
85  "HighNode" : "0",
86  "LedgerEntryType" : "RippleState",
87  "LowLimit" : {
88  "currency" : "USD",
89  "issuer" : "r32rQHyesiTtdWFU7UJVtff4nCR5SHCbJW",
90  "value" : "0"
91  },
92  "LowNode" : "0",
93  "index" : "D89BC239086183EB9458C396E643795C1134963E6550E682A190A5F021766D43"
94 })json",
95  R"json({
96  "Account" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
97  "BookDirectory" : "B025997A323F5C3E03DDF1334471F5984ABDE31C59D463525D038D7EA4C68000",
98  "BookNode" : "0",
99  "Flags" : 65536,
100  "LedgerEntryType" : "Offer",
101  "OwnerNode" : "0",
102  "Sequence" : 7,
103  "TakerGets" : {
104  "currency" : "USD",
105  "issuer" : "r32rQHyesiTtdWFU7UJVtff4nCR5SHCbJW",
106  "value" : "1"
107  },
108  "TakerPays" : "100000000",
109  "index" : "F03ABE26CB8C5F4AFB31A86590BD25C64C5756FCE5CE9704C27AFE291A4A29A1"
110 })json"};
111 
112 class AccountObjects_test : public beast::unit_test::suite
113 {
114 public:
115  void
117  {
118  testcase("error cases");
119 
120  using namespace jtx;
121  Env env(*this);
122 
123  // test error on no account
124  {
125  auto resp = env.rpc("json", "account_objects");
126  BEAST_EXPECT(resp[jss::error_message] == "Syntax error.");
127  }
128  // test error on malformed account string.
129  {
130  Json::Value params;
131  params[jss::account] =
132  "n94JNrQYkDrpt62bbSR7nVEhdyAvcJXRAsjEkFYyqRkh9SUTYEqV";
133  auto resp = env.rpc("json", "account_objects", to_string(params));
134  BEAST_EXPECT(
135  resp[jss::result][jss::error_message] == "Account malformed.");
136  }
137  // test error on account that's not in the ledger.
138  {
139  Json::Value params;
140  params[jss::account] = Account{"bogie"}.human();
141  auto resp = env.rpc("json", "account_objects", to_string(params));
142  BEAST_EXPECT(
143  resp[jss::result][jss::error_message] == "Account not found.");
144  }
145  Account const bob{"bob"};
146  // test error on large ledger_index.
147  {
148  Json::Value params;
149  params[jss::account] = bob.human();
150  params[jss::ledger_index] = 10;
151  auto resp = env.rpc("json", "account_objects", to_string(params));
152  BEAST_EXPECT(
153  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(
164  resp[jss::result][jss::error_message] ==
165  "Invalid field 'type', not string.");
166  }
167  // test error on type param not a valid type
168  {
169  Json::Value params;
170  params[jss::account] = bob.human();
171  params[jss::type] = "expedited";
172  auto resp = env.rpc("json", "account_objects", to_string(params));
173  BEAST_EXPECT(
174  resp[jss::result][jss::error_message] ==
175  "Invalid field 'type'.");
176  }
177  // test error on limit -ve
178  {
179  Json::Value params;
180  params[jss::account] = bob.human();
181  params[jss::limit] = -1;
182  auto resp = env.rpc("json", "account_objects", to_string(params));
183  BEAST_EXPECT(
184  resp[jss::result][jss::error_message] ==
185  "Invalid field 'limit', not unsigned integer.");
186  }
187  // test errors on marker
188  {
189  Account const gw{"G"};
190  env.fund(XRP(1000), gw);
191  auto const USD = gw["USD"];
192  env.trust(USD(1000), bob);
193  env(pay(gw, bob, XRP(1)));
194  env(offer(bob, XRP(100), bob["USD"](1)), txflags(tfPassive));
195 
196  Json::Value params;
197  params[jss::account] = bob.human();
198  params[jss::limit] = 1;
199  auto resp = env.rpc("json", "account_objects", to_string(params));
200 
201  auto resume_marker = resp[jss::result][jss::marker];
202  std::string mark = to_string(resume_marker);
203  params[jss::marker] = 10;
204  resp = env.rpc("json", "account_objects", to_string(params));
205  BEAST_EXPECT(
206  resp[jss::result][jss::error_message] ==
207  "Invalid field 'marker', not string.");
208 
209  params[jss::marker] = "This is a string with no comma";
210  resp = env.rpc("json", "account_objects", to_string(params));
211  BEAST_EXPECT(
212  resp[jss::result][jss::error_message] ==
213  "Invalid field 'marker'.");
214 
215  params[jss::marker] = "This string has a comma, but is not hex";
216  resp = env.rpc("json", "account_objects", to_string(params));
217  BEAST_EXPECT(
218  resp[jss::result][jss::error_message] ==
219  "Invalid field 'marker'.");
220 
221  params[jss::marker] = std::string(&mark[1U], 64);
222  resp = env.rpc("json", "account_objects", to_string(params));
223  BEAST_EXPECT(
224  resp[jss::result][jss::error_message] ==
225  "Invalid field 'marker'.");
226 
227  params[jss::marker] = std::string(&mark[1U], 65);
228  resp = env.rpc("json", "account_objects", to_string(params));
229  BEAST_EXPECT(
230  resp[jss::result][jss::error_message] ==
231  "Invalid field 'marker'.");
232 
233  params[jss::marker] = std::string(&mark[1U], 65) + "not hex";
234  resp = env.rpc("json", "account_objects", to_string(params));
235  BEAST_EXPECT(
236  resp[jss::result][jss::error_message] ==
237  "Invalid field 'marker'.");
238 
239  // Should this be an error?
240  // A hex digit is absent from the end of marker.
241  // No account objects returned.
242  params[jss::marker] = std::string(&mark[1U], 128);
243  resp = env.rpc("json", "account_objects", to_string(params));
244  BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 0);
245  }
246  }
247 
248  void
250  {
251  testcase("unsteppedThenStepped");
252 
253  using namespace jtx;
254  Env env(*this);
255 
256  Account const gw1{"G1"};
257  Account const gw2{"G2"};
258  Account const bob{"bob"};
259 
260  auto const USD1 = gw1["USD"];
261  auto const USD2 = gw2["USD"];
262 
263  env.fund(XRP(1000), gw1, gw2, bob);
264  env.trust(USD1(1000), bob);
265  env.trust(USD2(1000), bob);
266 
267  env(pay(gw1, bob, USD1(1000)));
268  env(pay(gw2, bob, USD2(1000)));
269 
270  env(offer(bob, XRP(100), bob["USD"](1)), txflags(tfPassive));
271  env(offer(bob, XRP(100), USD1(1)), txflags(tfPassive));
272 
273  Json::Value bobj[4];
274  for (int i = 0; i < 4; ++i)
275  Json::Reader{}.parse(bobs_account_objects[i], bobj[i]);
276 
277  // test 'unstepped'
278  // i.e. request account objects without explicit limit/marker paging
279  {
280  Json::Value params;
281  params[jss::account] = bob.human();
282  auto resp = env.rpc("json", "account_objects", to_string(params));
283  BEAST_EXPECT(!resp.isMember(jss::marker));
284 
285  BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 4);
286  for (int i = 0; i < 4; ++i)
287  {
288  auto& aobj = resp[jss::result][jss::account_objects][i];
289  aobj.removeMember("PreviousTxnID");
290  aobj.removeMember("PreviousTxnLgrSeq");
291  BEAST_EXPECT(aobj == bobj[i]);
292  }
293  }
294  // test request with type parameter as filter, unstepped
295  {
296  Json::Value params;
297  params[jss::account] = bob.human();
298  params[jss::type] = jss::state;
299  auto resp = env.rpc("json", "account_objects", to_string(params));
300  BEAST_EXPECT(!resp.isMember(jss::marker));
301 
302  BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 2);
303  for (int i = 0; i < 2; ++i)
304  {
305  auto& aobj = resp[jss::result][jss::account_objects][i];
306  aobj.removeMember("PreviousTxnID");
307  aobj.removeMember("PreviousTxnLgrSeq");
308  BEAST_EXPECT(aobj == bobj[i + 1]);
309  }
310  }
311  // test stepped one-at-a-time with limit=1, resume from prev marker
312  {
313  Json::Value params;
314  params[jss::account] = bob.human();
315  params[jss::limit] = 1;
316  for (int i = 0; i < 4; ++i)
317  {
318  auto resp =
319  env.rpc("json", "account_objects", to_string(params));
320  auto& aobjs = resp[jss::result][jss::account_objects];
321  BEAST_EXPECT(aobjs.size() == 1);
322  auto& aobj = aobjs[0U];
323  if (i < 3)
324  BEAST_EXPECT(resp[jss::result][jss::limit] == 1);
325  else
326  BEAST_EXPECT(!resp[jss::result].isMember(jss::limit));
327 
328  aobj.removeMember("PreviousTxnID");
329  aobj.removeMember("PreviousTxnLgrSeq");
330 
331  BEAST_EXPECT(aobj == bobj[i]);
332 
333  params[jss::marker] = resp[jss::result][jss::marker];
334  }
335  }
336  }
337 
338  void
340  {
341  // The preceding test case, unsteppedThenStepped(), found a bug in the
342  // support for NFToken Pages. So we're leaving that test alone when
343  // adding tests to exercise NFTokenPages.
344  testcase("unsteppedThenSteppedWithNFTs");
345 
346  using namespace jtx;
347  Env env(*this);
348 
349  Account const gw1{"G1"};
350  Account const gw2{"G2"};
351  Account const bob{"bob"};
352 
353  auto const USD1 = gw1["USD"];
354  auto const USD2 = gw2["USD"];
355 
356  env.fund(XRP(1000), gw1, gw2, bob);
357  env.close();
358 
359  // Check behavior if there are no account objects.
360  {
361  // Unpaged
362  Json::Value params;
363  params[jss::account] = bob.human();
364  auto resp = env.rpc("json", "account_objects", to_string(params));
365  BEAST_EXPECT(!resp.isMember(jss::marker));
366  BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 0);
367 
368  // Limit == 1
369  params[jss::limit] = 1;
370  resp = env.rpc("json", "account_objects", to_string(params));
371  BEAST_EXPECT(!resp.isMember(jss::marker));
372  BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 0);
373  }
374 
375  // Check behavior if there are only NFTokens.
376  env(token::mint(bob, 0u), txflags(tfTransferable));
377  env.close();
378 
379  // test 'unstepped'
380  // i.e. request account objects without explicit limit/marker paging
381  Json::Value unpaged;
382  {
383  Json::Value params;
384  params[jss::account] = bob.human();
385  auto resp = env.rpc("json", "account_objects", to_string(params));
386  BEAST_EXPECT(!resp.isMember(jss::marker));
387 
388  unpaged = resp[jss::result][jss::account_objects];
389  BEAST_EXPECT(unpaged.size() == 1);
390  }
391  // test request with type parameter as filter, unstepped
392  {
393  Json::Value params;
394  params[jss::account] = bob.human();
395  params[jss::type] = jss::nft_page;
396  auto resp = env.rpc("json", "account_objects", to_string(params));
397  BEAST_EXPECT(!resp.isMember(jss::marker));
398  Json::Value& aobjs = resp[jss::result][jss::account_objects];
399  BEAST_EXPECT(aobjs.size() == 1);
400  BEAST_EXPECT(
401  aobjs[0u][sfLedgerEntryType.jsonName] == jss::NFTokenPage);
402  BEAST_EXPECT(aobjs[0u][sfNFTokens.jsonName].size() == 1);
403  }
404  // test stepped one-at-a-time with limit=1, resume from prev marker
405  {
406  Json::Value params;
407  params[jss::account] = bob.human();
408  params[jss::limit] = 1;
409 
410  Json::Value resp =
411  env.rpc("json", "account_objects", to_string(params));
412  Json::Value& aobjs = resp[jss::result][jss::account_objects];
413  BEAST_EXPECT(aobjs.size() == 1);
414  auto& aobj = aobjs[0U];
415  BEAST_EXPECT(!resp[jss::result].isMember(jss::limit));
416  BEAST_EXPECT(!resp[jss::result].isMember(jss::marker));
417 
418  BEAST_EXPECT(aobj == unpaged[0u]);
419  }
420 
421  // Add more objects in addition to the NFToken Page.
422  env.trust(USD1(1000), bob);
423  env.trust(USD2(1000), bob);
424 
425  env(pay(gw1, bob, USD1(1000)));
426  env(pay(gw2, bob, USD2(1000)));
427 
428  env(offer(bob, XRP(100), bob["USD"](1)), txflags(tfPassive));
429  env(offer(bob, XRP(100), USD1(1)), txflags(tfPassive));
430  env.close();
431 
432  // test 'unstepped'
433  {
434  Json::Value params;
435  params[jss::account] = bob.human();
436  auto resp = env.rpc("json", "account_objects", to_string(params));
437  BEAST_EXPECT(!resp.isMember(jss::marker));
438 
439  unpaged = resp[jss::result][jss::account_objects];
440  BEAST_EXPECT(unpaged.size() == 5);
441  }
442  // test request with type parameter as filter, unstepped
443  {
444  Json::Value params;
445  params[jss::account] = bob.human();
446  params[jss::type] = jss::nft_page;
447  auto resp = env.rpc("json", "account_objects", to_string(params));
448  BEAST_EXPECT(!resp.isMember(jss::marker));
449  Json::Value& aobjs = resp[jss::result][jss::account_objects];
450  BEAST_EXPECT(aobjs.size() == 1);
451  BEAST_EXPECT(
452  aobjs[0u][sfLedgerEntryType.jsonName] == jss::NFTokenPage);
453  BEAST_EXPECT(aobjs[0u][sfNFTokens.jsonName].size() == 1);
454  }
455  // test stepped one-at-a-time with limit=1, resume from prev marker
456  {
457  Json::Value params;
458  params[jss::account] = bob.human();
459  params[jss::limit] = 1;
460  for (int i = 0; i < 5; ++i)
461  {
462  Json::Value resp =
463  env.rpc("json", "account_objects", to_string(params));
464  Json::Value& aobjs = resp[jss::result][jss::account_objects];
465  BEAST_EXPECT(aobjs.size() == 1);
466  auto& aobj = aobjs[0U];
467  if (i < 4)
468  {
469  BEAST_EXPECT(resp[jss::result][jss::limit] == 1);
470  BEAST_EXPECT(resp[jss::result].isMember(jss::marker));
471  }
472  else
473  {
474  BEAST_EXPECT(!resp[jss::result].isMember(jss::limit));
475  BEAST_EXPECT(!resp[jss::result].isMember(jss::marker));
476  }
477 
478  BEAST_EXPECT(aobj == unpaged[i]);
479 
480  params[jss::marker] = resp[jss::result][jss::marker];
481  }
482  }
483 
484  // Make sure things still work if there is more than 1 NFT Page.
485  for (int i = 0; i < 32; ++i)
486  {
487  env(token::mint(bob, 0u), txflags(tfTransferable));
488  env.close();
489  }
490  // test 'unstepped'
491  {
492  Json::Value params;
493  params[jss::account] = bob.human();
494  auto resp = env.rpc("json", "account_objects", to_string(params));
495  BEAST_EXPECT(!resp.isMember(jss::marker));
496 
497  unpaged = resp[jss::result][jss::account_objects];
498  BEAST_EXPECT(unpaged.size() == 6);
499  }
500  // test request with type parameter as filter, unstepped
501  {
502  Json::Value params;
503  params[jss::account] = bob.human();
504  params[jss::type] = jss::nft_page;
505  auto resp = env.rpc("json", "account_objects", to_string(params));
506  BEAST_EXPECT(!resp.isMember(jss::marker));
507  Json::Value& aobjs = resp[jss::result][jss::account_objects];
508  BEAST_EXPECT(aobjs.size() == 2);
509  }
510  // test stepped one-at-a-time with limit=1, resume from prev marker
511  {
512  Json::Value params;
513  params[jss::account] = bob.human();
514  params[jss::limit] = 1;
515  for (int i = 0; i < 6; ++i)
516  {
517  Json::Value resp =
518  env.rpc("json", "account_objects", to_string(params));
519  Json::Value& aobjs = resp[jss::result][jss::account_objects];
520  BEAST_EXPECT(aobjs.size() == 1);
521  auto& aobj = aobjs[0U];
522  if (i < 5)
523  {
524  BEAST_EXPECT(resp[jss::result][jss::limit] == 1);
525  BEAST_EXPECT(resp[jss::result].isMember(jss::marker));
526  }
527  else
528  {
529  BEAST_EXPECT(!resp[jss::result].isMember(jss::limit));
530  BEAST_EXPECT(!resp[jss::result].isMember(jss::marker));
531  }
532 
533  BEAST_EXPECT(aobj == unpaged[i]);
534 
535  params[jss::marker] = resp[jss::result][jss::marker];
536  }
537  }
538  }
539 
540  void
542  {
543  testcase("object types");
544 
545  // Give gw a bunch of ledger objects and make sure we can retrieve
546  // them by type.
547  using namespace jtx;
548 
549  Account const alice{"alice"};
550  Account const gw{"gateway"};
551  auto const USD = gw["USD"];
552 
553  Env env(*this);
554 
555  // Make a lambda we can use to get "account_objects" easily.
556  auto acct_objs = [&env](
557  AccountID const& acct,
559  std::optional<std::uint16_t> limit = std::nullopt,
560  std::optional<std::string> marker = std::nullopt) {
561  Json::Value params;
562  params[jss::account] = to_string(acct);
563  if (type)
564  params[jss::type] = *type;
565  if (limit)
566  params[jss::limit] = *limit;
567  if (marker)
568  params[jss::marker] = *marker;
569  params[jss::ledger_index] = "validated";
570  return env.rpc("json", "account_objects", to_string(params));
571  };
572 
573  // Make a lambda that easily identifies the size of account objects.
574  auto acct_objs_is_size = [](Json::Value const& resp, unsigned size) {
575  return resp[jss::result][jss::account_objects].isArray() &&
576  (resp[jss::result][jss::account_objects].size() == size);
577  };
578 
579  env.fund(XRP(10000), gw, alice);
580  env.close();
581 
582  // Since the account is empty now, all account objects should come
583  // back empty.
584  BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::account), 0));
585  BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::amendments), 0));
586  BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::check), 0));
587  BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::deposit_preauth), 0));
588  BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::directory), 0));
589  BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::escrow), 0));
590  BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::fee), 0));
591  BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::hashes), 0));
592  BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::nft_page), 0));
593  BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::offer), 0));
594  BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::payment_channel), 0));
595  BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::signer_list), 0));
596  BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::state), 0));
597  BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::ticket), 0));
598  BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::amm), 0));
599 
600  // gw mints an NFT so we can find it.
601  uint256 const nftID{token::getNextID(env, gw, 0u, tfTransferable)};
602  env(token::mint(gw, 0u), txflags(tfTransferable));
603  env.close();
604  {
605  // Find the NFToken page and make sure it's the right one.
606  Json::Value const resp = acct_objs(gw, jss::nft_page);
607  BEAST_EXPECT(acct_objs_is_size(resp, 1));
608 
609  auto const& nftPage = resp[jss::result][jss::account_objects][0u];
610  BEAST_EXPECT(nftPage[sfNFTokens.jsonName].size() == 1);
611  BEAST_EXPECT(
612  nftPage[sfNFTokens.jsonName][0u][sfNFToken.jsonName]
613  [sfNFTokenID.jsonName] == to_string(nftID));
614  }
615 
616  // Set up a trust line so we can find it.
617  env.trust(USD(1000), alice);
618  env.close();
619  env(pay(gw, alice, USD(5)));
620  env.close();
621  {
622  // Find the trustline and make sure it's the right one.
623  Json::Value const resp = acct_objs(gw, jss::state);
624  BEAST_EXPECT(acct_objs_is_size(resp, 1));
625 
626  auto const& state = resp[jss::result][jss::account_objects][0u];
627  BEAST_EXPECT(state[sfBalance.jsonName][jss::value].asInt() == -5);
628  BEAST_EXPECT(
629  state[sfHighLimit.jsonName][jss::value].asUInt() == 1000);
630  }
631  // gw writes a check for USD(10) to alice.
632  env(check::create(gw, alice, USD(10)));
633  env.close();
634  {
635  // Find the check.
636  Json::Value const resp = acct_objs(gw, jss::check);
637  BEAST_EXPECT(acct_objs_is_size(resp, 1));
638 
639  auto const& check = resp[jss::result][jss::account_objects][0u];
640  BEAST_EXPECT(check[sfAccount.jsonName] == gw.human());
641  BEAST_EXPECT(check[sfDestination.jsonName] == alice.human());
642  BEAST_EXPECT(check[sfSendMax.jsonName][jss::value].asUInt() == 10);
643  }
644  // gw preauthorizes payments from alice.
645  env(deposit::auth(gw, alice));
646  env.close();
647  {
648  // Find the preauthorization.
649  Json::Value const resp = acct_objs(gw, jss::deposit_preauth);
650  BEAST_EXPECT(acct_objs_is_size(resp, 1));
651 
652  auto const& preauth = resp[jss::result][jss::account_objects][0u];
653  BEAST_EXPECT(preauth[sfAccount.jsonName] == gw.human());
654  BEAST_EXPECT(preauth[sfAuthorize.jsonName] == alice.human());
655  }
656  {
657  // gw creates an escrow that we can look for in the ledger.
658  Json::Value jvEscrow;
659  jvEscrow[jss::TransactionType] = jss::EscrowCreate;
660  jvEscrow[jss::Flags] = tfUniversal;
661  jvEscrow[jss::Account] = gw.human();
662  jvEscrow[jss::Destination] = gw.human();
663  jvEscrow[jss::Amount] = XRP(100).value().getJson(JsonOptions::none);
664  jvEscrow[sfFinishAfter.jsonName] =
665  env.now().time_since_epoch().count() + 1;
666  env(jvEscrow);
667  env.close();
668  }
669  {
670  // Find the escrow.
671  Json::Value const resp = acct_objs(gw, jss::escrow);
672  BEAST_EXPECT(acct_objs_is_size(resp, 1));
673 
674  auto const& escrow = resp[jss::result][jss::account_objects][0u];
675  BEAST_EXPECT(escrow[sfAccount.jsonName] == gw.human());
676  BEAST_EXPECT(escrow[sfDestination.jsonName] == gw.human());
677  BEAST_EXPECT(escrow[sfAmount.jsonName].asUInt() == 100'000'000);
678  }
679  // gw creates an offer that we can look for in the ledger.
680  env(offer(gw, USD(7), XRP(14)));
681  env.close();
682  {
683  // Find the offer.
684  Json::Value const resp = acct_objs(gw, jss::offer);
685  BEAST_EXPECT(acct_objs_is_size(resp, 1));
686 
687  auto const& offer = resp[jss::result][jss::account_objects][0u];
688  BEAST_EXPECT(offer[sfAccount.jsonName] == gw.human());
689  BEAST_EXPECT(offer[sfTakerGets.jsonName].asUInt() == 14'000'000);
690  BEAST_EXPECT(offer[sfTakerPays.jsonName][jss::value].asUInt() == 7);
691  }
692  {
693  // Create a payment channel from qw to alice that we can look for.
694  Json::Value jvPayChan;
695  jvPayChan[jss::TransactionType] = jss::PaymentChannelCreate;
696  jvPayChan[jss::Flags] = tfUniversal;
697  jvPayChan[jss::Account] = gw.human();
698  jvPayChan[jss::Destination] = alice.human();
699  jvPayChan[jss::Amount] =
700  XRP(300).value().getJson(JsonOptions::none);
701  jvPayChan[sfSettleDelay.jsonName] = 24 * 60 * 60;
702  jvPayChan[sfPublicKey.jsonName] = strHex(gw.pk().slice());
703  env(jvPayChan);
704  env.close();
705  }
706  {
707  // Find the payment channel.
708  Json::Value const resp = acct_objs(gw, jss::payment_channel);
709  BEAST_EXPECT(acct_objs_is_size(resp, 1));
710 
711  auto const& payChan = resp[jss::result][jss::account_objects][0u];
712  BEAST_EXPECT(payChan[sfAccount.jsonName] == gw.human());
713  BEAST_EXPECT(payChan[sfAmount.jsonName].asUInt() == 300'000'000);
714  BEAST_EXPECT(
715  payChan[sfSettleDelay.jsonName].asUInt() == 24 * 60 * 60);
716  }
717  // Make gw multisigning by adding a signerList.
718  env(signers(gw, 6, {{alice, 7}}));
719  env.close();
720  {
721  // Find the signer list.
722  Json::Value const resp = acct_objs(gw, jss::signer_list);
723  BEAST_EXPECT(acct_objs_is_size(resp, 1));
724 
725  auto const& signerList =
726  resp[jss::result][jss::account_objects][0u];
727  BEAST_EXPECT(signerList[sfSignerQuorum.jsonName] == 6);
728  auto const& entry = signerList[sfSignerEntries.jsonName][0u]
730  BEAST_EXPECT(entry[sfAccount.jsonName] == alice.human());
731  BEAST_EXPECT(entry[sfSignerWeight.jsonName].asUInt() == 7);
732  }
733  // Create a Ticket for gw.
734  env(ticket::create(gw, 1));
735  env.close();
736  {
737  // Find the ticket.
738  Json::Value const resp = acct_objs(gw, jss::ticket);
739  BEAST_EXPECT(acct_objs_is_size(resp, 1));
740 
741  auto const& ticket = resp[jss::result][jss::account_objects][0u];
742  BEAST_EXPECT(ticket[sfAccount.jsonName] == gw.human());
743  BEAST_EXPECT(ticket[sfLedgerEntryType.jsonName] == jss::Ticket);
744  BEAST_EXPECT(ticket[sfTicketSequence.jsonName].asUInt() == 13);
745  }
746  {
747  // See how "deletion_blockers_only" handles gw's directory.
748  Json::Value params;
749  params[jss::account] = gw.human();
750  params[jss::deletion_blockers_only] = true;
751  auto resp = env.rpc("json", "account_objects", to_string(params));
752 
753  std::vector<std::string> const expectedLedgerTypes = [] {
755  jss::Escrow.c_str(),
756  jss::Check.c_str(),
757  jss::NFTokenPage.c_str(),
758  jss::RippleState.c_str(),
759  jss::PayChannel.c_str()};
760  std::sort(v.begin(), v.end());
761  return v;
762  }();
763 
764  std::uint32_t const expectedAccountObjects{
765  static_cast<std::uint32_t>(std::size(expectedLedgerTypes))};
766 
767  if (BEAST_EXPECT(acct_objs_is_size(resp, expectedAccountObjects)))
768  {
769  auto const& aobjs = resp[jss::result][jss::account_objects];
770  std::vector<std::string> gotLedgerTypes;
771  gotLedgerTypes.reserve(expectedAccountObjects);
772  for (std::uint32_t i = 0; i < expectedAccountObjects; ++i)
773  {
774  gotLedgerTypes.push_back(
775  aobjs[i]["LedgerEntryType"].asString());
776  }
777  std::sort(gotLedgerTypes.begin(), gotLedgerTypes.end());
778  BEAST_EXPECT(gotLedgerTypes == expectedLedgerTypes);
779  }
780  }
781  {
782  // See how "deletion_blockers_only" with `type` handles gw's
783  // directory.
784  Json::Value params;
785  params[jss::account] = gw.human();
786  params[jss::deletion_blockers_only] = true;
787  params[jss::type] = jss::escrow;
788  auto resp = env.rpc("json", "account_objects", to_string(params));
789 
790  if (BEAST_EXPECT(acct_objs_is_size(resp, 1u)))
791  {
792  auto const& aobjs = resp[jss::result][jss::account_objects];
793  BEAST_EXPECT(aobjs[0u]["LedgerEntryType"] == jss::Escrow);
794  }
795  }
796  {
797  // Make a lambda to get the types
798  auto getTypes = [&](Json::Value const& resp,
799  std::vector<std::string>& typesOut) {
800  auto const objs = resp[jss::result][jss::account_objects];
801  for (auto const& obj : resp[jss::result][jss::account_objects])
802  typesOut.push_back(
803  obj[sfLedgerEntryType.fieldName].asString());
804  std::sort(typesOut.begin(), typesOut.end());
805  };
806  // Make a lambda we can use to check the number of fetched
807  // account objects and their ledger type
808  auto expectObjects =
809  [&](Json::Value const& resp,
810  std::vector<std::string> const& types) -> bool {
811  if (!acct_objs_is_size(resp, types.size()))
812  return false;
813  std::vector<std::string> typesOut;
814  getTypes(resp, typesOut);
815  return types == typesOut;
816  };
817  // Find AMM objects
818  AMM amm(env, gw, XRP(1'000), USD(1'000));
819  amm.deposit(alice, USD(1));
820  // AMM account has 4 objects: AMM object and 3 trustlines
821  auto const lines = getAccountLines(env, amm.ammAccount());
822  BEAST_EXPECT(lines[jss::lines].size() == 3);
823  // request AMM only, doesn't depend on the limit
824  BEAST_EXPECT(
825  acct_objs_is_size(acct_objs(amm.ammAccount(), jss::amm), 1));
826  // request first two objects
827  auto resp = acct_objs(amm.ammAccount(), std::nullopt, 2);
828  std::vector<std::string> typesOut;
829  getTypes(resp, typesOut);
830  // request next two objects
831  resp = acct_objs(
832  amm.ammAccount(),
833  std::nullopt,
834  10,
835  resp[jss::result][jss::marker].asString());
836  getTypes(resp, typesOut);
837  BEAST_EXPECT(
838  (typesOut ==
840  jss::AMM.c_str(),
841  jss::RippleState.c_str(),
842  jss::RippleState.c_str(),
843  jss::RippleState.c_str()}));
844  // filter by state: there are three trustlines
845  resp = acct_objs(amm.ammAccount(), jss::state, 10);
846  BEAST_EXPECT(expectObjects(
847  resp,
848  {jss::RippleState.c_str(),
849  jss::RippleState.c_str(),
850  jss::RippleState.c_str()}));
851  // AMM account doesn't own offers
852  BEAST_EXPECT(
853  acct_objs_is_size(acct_objs(amm.ammAccount(), jss::offer), 0));
854  // gw account doesn't own AMM object
855  BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::amm), 0));
856  }
857 
858  // Run up the number of directory entries so gw has two
859  // directory nodes.
860  for (int d = 1'000'032; d >= 1'000'000; --d)
861  {
862  env(offer(gw, USD(1), drops(d)));
863  env.close();
864  }
865 
866  // Verify that the non-returning types still don't return anything.
867  BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::account), 0));
868  BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::amendments), 0));
869  BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::directory), 0));
870  BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::fee), 0));
871  BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::hashes), 0));
872  }
873 
874  void
875  run() override
876  {
877  testErrors();
880  testObjectTypes();
881  }
882 };
883 
884 BEAST_DEFINE_TESTSUITE(AccountObjects, app, ripple);
885 
886 } // namespace test
887 } // namespace ripple
ripple::sfSignerWeight
const SF_UINT16 sfSignerWeight
ripple::test::AccountObjects_test::testObjectTypes
void testObjectTypes()
Definition: AccountObjects_test.cpp:541
ripple::tfTransferable
constexpr const std::uint32_t tfTransferable
Definition: TxFlags.h:131
ripple::test::jtx::XRP
const XRP_t XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
ripple::sfSendMax
const SF_AMOUNT sfSendMax
std::string
STL class.
ripple::test::jtx::drops
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Definition: amount.h:241
ripple::sfDestination
const SF_ACCOUNT sfDestination
ripple::sfAmount
const SF_AMOUNT sfAmount
ripple::sfNFTokenID
const SF_UINT256 sfNFTokenID
std::vector::reserve
T reserve(T... args)
ripple::test::AccountObjects_test
Definition: AccountObjects_test.cpp:112
std::vector< std::string >
std::size
T size(T... args)
ripple::SField::fieldName
const std::string fieldName
Definition: SField.h:135
ripple::test::jtx::Account::human
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:113
ripple::sfTicketSequence
const SF_UINT32 sfTicketSequence
ripple::tfPassive
constexpr std::uint32_t tfPassive
Definition: TxFlags.h:94
ripple::test::jtx::AMM
Convenience class to test AMM functionality.
Definition: AMM.h:62
Json::Reader
Unserialize a JSON document into a Value.
Definition: json_reader.h:36
ripple::SField::jsonName
const Json::StaticString jsonName
Definition: SField.h:139
std::sort
T sort(T... args)
ripple::test::jtx::Env::trust
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:259
algorithm
ripple::sfSignerQuorum
const SF_UINT32 sfSignerQuorum
ripple::test::AccountObjects_test::testUnsteppedThenStepped
void testUnsteppedThenStepped()
Definition: AccountObjects_test.cpp:249
std::vector::push_back
T push_back(T... args)
ripple::test::AccountObjects_test::testErrors
void testErrors()
Definition: AccountObjects_test.cpp:116
ripple::base_uint< 160, detail::AccountIDTag >
ripple::sfTakerPays
const SF_AMOUNT sfTakerPays
std::chrono::time_point::time_since_epoch
T time_since_epoch(T... args)
ripple::sfSettleDelay
const SF_UINT32 sfSettleDelay
ripple::JsonOptions::none
@ none
ripple::test::AccountObjects_test::testUnsteppedThenSteppedWithNFTs
void testUnsteppedThenSteppedWithNFTs()
Definition: AccountObjects_test.cpp:339
ripple::test::jtx::txflags
Set the flags on a JTx.
Definition: txflags.h:30
ripple::test::jtx::Env::close
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:121
Json::Value::size
UInt size() const
Number of values in array or object.
Definition: json_value.cpp:706
ripple::sfTakerGets
const SF_AMOUNT sfTakerGets
ripple::sfAuthorize
const SF_ACCOUNT sfAuthorize
std::uint32_t
ripple::sfHighLimit
const SF_AMOUNT sfHighLimit
ripple::sfSignerEntry
const SField sfSignerEntry
ripple::sfNFToken
const SField sfNFToken
Json::Value::isArray
bool isArray() const
Definition: json_value.cpp:1015
ripple::sfNFTokens
const SField sfNFTokens
ripple::sfSignerEntries
const SField sfSignerEntries
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::sfLedgerEntryType
const SF_UINT16 sfLedgerEntryType
ripple::test::jtx::Env::now
NetClock::time_point now()
Returns the current network time.
Definition: Env.h:265
std::vector::begin
T begin(T... args)
ripple::test::jtx::Env::fund
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:228
Json::Reader::parse
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Definition: json_reader.cpp:74
ripple::test::AccountObjects_test::run
void run() override
Definition: AccountObjects_test.cpp:875
ripple::sfBalance
const SF_AMOUNT sfBalance
std::optional
ripple::to_string
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
Definition: app/misc/impl/Manifest.cpp:41
ripple::sfFinishAfter
const SF_UINT32 sfFinishAfter
ripple::test::jtx::Account
Immutable cryptographic account descriptor.
Definition: Account.h:37
ripple::sfAccount
const SF_ACCOUNT sfAccount
ripple::strHex
std::string strHex(FwdIt begin, FwdIt end)
Definition: strHex.h:30
std::vector::end
T end(T... args)
ripple::tfUniversal
constexpr std::uint32_t tfUniversal
Definition: TxFlags.h:59
ripple::test::bobs_account_objects
static char const * bobs_account_objects[]
Definition: AccountObjects_test.cpp:34
ripple::sfPublicKey
const SF_VL sfPublicKey
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:117
ripple::test::jtx::getAccountLines
Json::Value getAccountLines(Env &env, AccountID const &acctId)
Definition: TestHelpers.cpp:40
ripple::test::jtx::Env::rpc
Json::Value rpc(std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:700
Json::Value
Represents a JSON value.
Definition: json_value.h:145
ripple::test::jtx::owner_count
Definition: owners.h:49
Json::Value::asString
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:469
ripple::test::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(DeliverMin, app, ripple)