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