rippled
AccountOffers_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/protocol/jss.h>
21 #include <test/jtx.h>
22 #include <test/jtx/TestHelpers.h>
23 
24 namespace ripple {
25 namespace test {
26 
27 class AccountOffers_test : public beast::unit_test::suite
28 {
29 public:
30  // test helper
31  static bool
33  {
34  return val.isMember(jss::marker) && val[jss::marker].isString() &&
35  val[jss::marker].asString().size() > 0;
36  }
37 
38  void
40  {
41  using namespace jtx;
42  Env env{*this, envconfig(no_admin)};
43  Account const gw("G1");
44  auto const USD_gw = gw["USD"];
45  Account const bob("bob");
46  auto const USD_bob = bob["USD"];
47 
48  env.fund(XRP(10000), gw, bob);
49  env.trust(USD_gw(1000), bob);
50 
51  // this is to provide some USD from gw in the
52  // bob account so that it can rightly
53  // make offers that give those USDs
54  env(pay(gw, bob, USD_gw(10)));
55  unsigned const offer_count = 12u;
56  for (auto i = 0u; i < offer_count; i++)
57  {
58  Json::Value jvo = offer(bob, XRP(100 + i), USD_gw(1));
59  jvo[sfExpiration.fieldName] = 10000000u;
60  env(jvo);
61  }
62 
63  // make non-limited RPC call
64  auto const jro_nl =
65  env.rpc("account_offers", bob.human())[jss::result][jss::offers];
66  BEAST_EXPECT(checkArraySize(jro_nl, offer_count));
67 
68  // now make a low-limit query, should get "corrected"
69  // to a min of 10 results with a marker set since there
70  // are more than 10 total
71  Json::Value jvParams;
72  jvParams[jss::account] = bob.human();
73  jvParams[jss::limit] = 1u;
74  auto const jrr_l = env.rpc(
75  "json", "account_offers", jvParams.toStyledString())[jss::result];
76  auto const& jro_l = jrr_l[jss::offers];
77  BEAST_EXPECT(checkMarker(jrr_l));
78  // 9u is the expected size, since one account object is a trustline
79  BEAST_EXPECT(checkArraySize(jro_l, 9u));
80  }
81 
82  void
83  testSequential(bool asAdmin)
84  {
85  using namespace jtx;
86  Env env{*this, asAdmin ? envconfig() : envconfig(no_admin)};
87  Account const gw("G1");
88  auto const USD_gw = gw["USD"];
89  Account const bob("bob");
90  auto const USD_bob = bob["USD"];
91 
92  env.fund(XRP(10000), gw, bob);
93  env.trust(USD_gw(1000), bob);
94 
95  // this is to provide some USD from gw in the
96  // bob account so that it can rightly
97  // make offers that give those USDs
98  env(pay(gw, bob, USD_gw(10)));
99 
100  env(offer(bob, XRP(100), USD_bob(1)));
101  env(offer(bob, XRP(200), USD_gw(2)));
102  env(offer(bob, XRP(30), USD_gw(6)));
103 
104  // make the RPC call
105  auto const jroOuter =
106  env.rpc("account_offers", bob.human())[jss::result][jss::offers];
107  if (BEAST_EXPECT(checkArraySize(jroOuter, 3u)))
108  {
109  // Note that the returned offers are sorted by index, not by
110  // order of insertion or by sequence number. There is no
111  // guarantee that their order will not change in the future
112  // if the sequence numbers or the account IDs change.
113  BEAST_EXPECT(jroOuter[0u][jss::quality] == "100000000");
114  BEAST_EXPECT(jroOuter[0u][jss::taker_gets][jss::currency] == "USD");
115  BEAST_EXPECT(
116  jroOuter[0u][jss::taker_gets][jss::issuer] == gw.human());
117  BEAST_EXPECT(jroOuter[0u][jss::taker_gets][jss::value] == "2");
118  BEAST_EXPECT(jroOuter[0u][jss::taker_pays] == "200000000");
119 
120  BEAST_EXPECT(jroOuter[1u][jss::quality] == "100000000");
121  BEAST_EXPECT(jroOuter[1u][jss::taker_gets][jss::currency] == "USD");
122  BEAST_EXPECT(
123  jroOuter[1u][jss::taker_gets][jss::issuer] == bob.human());
124  BEAST_EXPECT(jroOuter[1u][jss::taker_gets][jss::value] == "1");
125  BEAST_EXPECT(jroOuter[1u][jss::taker_pays] == "100000000");
126 
127  BEAST_EXPECT(jroOuter[2u][jss::quality] == "5000000");
128  BEAST_EXPECT(jroOuter[2u][jss::taker_gets][jss::currency] == "USD");
129  BEAST_EXPECT(
130  jroOuter[2u][jss::taker_gets][jss::issuer] == gw.human());
131  BEAST_EXPECT(jroOuter[2u][jss::taker_gets][jss::value] == "6");
132  BEAST_EXPECT(jroOuter[2u][jss::taker_pays] == "30000000");
133  }
134 
135  {
136  // now make a limit (= 1) query for the same data
137  Json::Value jvParams;
138  jvParams[jss::account] = bob.human();
139  jvParams[jss::limit] = 1u;
140  auto const jrr_l_1 = env.rpc(
141  "json",
142  "account_offers",
143  jvParams.toStyledString())[jss::result];
144  auto const& jro_l_1 = jrr_l_1[jss::offers];
145  // there is a difference in the validation of the limit param
146  // between admin and non-admin requests. with admin requests, the
147  // limit parameter is NOT subject to sane defaults, but with a
148  // non-admin there are pre-configured limit ranges applied. That's
149  // why we have different BEAST_EXPECT()s here for the two scenarios
150  BEAST_EXPECT(checkArraySize(jro_l_1, asAdmin ? 1u : 3u));
151  BEAST_EXPECT(
152  asAdmin ? checkMarker(jrr_l_1)
153  : (!jrr_l_1.isMember(jss::marker)));
154  if (asAdmin)
155  {
156  BEAST_EXPECT(jroOuter[0u] == jro_l_1[0u]);
157 
158  // second item...with previous marker passed
159  jvParams[jss::marker] = jrr_l_1[jss::marker];
160  auto const jrr_l_2 = env.rpc(
161  "json",
162  "account_offers",
163  jvParams.toStyledString())[jss::result];
164  auto const& jro_l_2 = jrr_l_2[jss::offers];
165  BEAST_EXPECT(checkMarker(jrr_l_2));
166  BEAST_EXPECT(checkArraySize(jro_l_2, 1u));
167  BEAST_EXPECT(jroOuter[1u] == jro_l_2[0u]);
168 
169  // last item...with previous marker passed
170  jvParams[jss::marker] = jrr_l_2[jss::marker];
171  jvParams[jss::limit] = 10u;
172  auto const jrr_l_3 = env.rpc(
173  "json",
174  "account_offers",
175  jvParams.toStyledString())[jss::result];
176  auto const& jro_l_3 = jrr_l_3[jss::offers];
177  BEAST_EXPECT(!jrr_l_3.isMember(jss::marker));
178  BEAST_EXPECT(checkArraySize(jro_l_3, 1u));
179  BEAST_EXPECT(jroOuter[2u] == jro_l_3[0u]);
180  }
181  else
182  {
183  BEAST_EXPECT(jroOuter == jro_l_1);
184  }
185  }
186 
187  {
188  // now make a limit (= 0) query for the same data
189  // since we operate on the admin port, the limit
190  // value of 0 is not adjusted into tuned ranges for admin requests
191  // so we literally get 0 elements in that case. For non-admin
192  // requests, we get limit defaults applied thus all our results
193  // come back (we are below the min results limit)
194  Json::Value jvParams;
195  jvParams[jss::account] = bob.human();
196  jvParams[jss::limit] = 0u;
197  auto const jrr = env.rpc(
198  "json",
199  "account_offers",
200  jvParams.toStyledString())[jss::result];
201  auto const& jro = jrr[jss::offers];
202  if (asAdmin)
203  {
204  // limit == 0 is invalid
205  BEAST_EXPECT(jrr.isMember(jss::error_message));
206  }
207  else
208  {
209  // Call should enforce min limit of 10
210  BEAST_EXPECT(checkArraySize(jro, 3u));
211  BEAST_EXPECT(!jrr.isMember(jss::marker));
212  }
213  }
214  }
215 
216  void
218  {
219  using namespace jtx;
220  Env env(*this);
221  Account const gw("G1");
222  auto const USD_gw = gw["USD"];
223  Account const bob("bob");
224  auto const USD_bob = bob["USD"];
225 
226  env.fund(XRP(10000), gw, bob);
227  env.trust(USD_gw(1000), bob);
228 
229  {
230  // no account field
231  auto const jrr = env.rpc("account_offers");
232  BEAST_EXPECT(jrr[jss::error] == "badSyntax");
233  BEAST_EXPECT(jrr[jss::status] == "error");
234  BEAST_EXPECT(jrr[jss::error_message] == "Syntax error.");
235  }
236 
237  {
238  // empty string account
239  Json::Value jvParams;
240  jvParams[jss::account] = "";
241  auto const jrr = env.rpc(
242  "json",
243  "account_offers",
244  jvParams.toStyledString())[jss::result];
245  BEAST_EXPECT(jrr[jss::error] == "actMalformed");
246  BEAST_EXPECT(jrr[jss::status] == "error");
247  BEAST_EXPECT(jrr[jss::error_message] == "Account malformed.");
248  }
249 
250  {
251  // bogus account value
252  auto const jrr = env.rpc(
253  "account_offers", Account("bogus").human())[jss::result];
254  BEAST_EXPECT(jrr[jss::error] == "actNotFound");
255  BEAST_EXPECT(jrr[jss::status] == "error");
256  BEAST_EXPECT(jrr[jss::error_message] == "Account not found.");
257  }
258 
259  {
260  // bad limit
261  Json::Value jvParams;
262  jvParams[jss::account] = bob.human();
263  jvParams[jss::limit] = "0"; // NOT an integer
264  auto const jrr = env.rpc(
265  "json",
266  "account_offers",
267  jvParams.toStyledString())[jss::result];
268  BEAST_EXPECT(jrr[jss::error] == "invalidParams");
269  BEAST_EXPECT(jrr[jss::status] == "error");
270  BEAST_EXPECT(
271  jrr[jss::error_message] ==
272  "Invalid field 'limit', not unsigned integer.");
273  }
274 
275  {
276  // invalid marker
277  Json::Value jvParams;
278  jvParams[jss::account] = bob.human();
279  jvParams[jss::marker] = "NOT_A_MARKER";
280  auto const jrr = env.rpc(
281  "json",
282  "account_offers",
283  jvParams.toStyledString())[jss::result];
284  BEAST_EXPECT(jrr[jss::error] == "invalidParams");
285  BEAST_EXPECT(jrr[jss::status] == "error");
286  BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
287  }
288 
289  {
290  // invalid marker - not a string
291  Json::Value jvParams;
292  jvParams[jss::account] = bob.human();
293  jvParams[jss::marker] = 1;
294  auto const jrr = env.rpc(
295  "json",
296  "account_offers",
297  jvParams.toStyledString())[jss::result];
298  BEAST_EXPECT(jrr[jss::error] == "invalidParams");
299  BEAST_EXPECT(jrr[jss::status] == "error");
300  BEAST_EXPECT(
301  jrr[jss::error_message] ==
302  "Invalid field 'marker', not string.");
303  }
304 
305  {
306  // ask for a bad ledger index
307  Json::Value jvParams;
308  jvParams[jss::account] = bob.human();
309  jvParams[jss::ledger_index] = 10u;
310  auto const jrr = env.rpc(
311  "json",
312  "account_offers",
313  jvParams.toStyledString())[jss::result];
314  BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
315  BEAST_EXPECT(jrr[jss::status] == "error");
316  BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
317  }
318  }
319 
320  void
321  run() override
322  {
323  testSequential(true);
324  testSequential(false);
325  testBadInput();
327  }
328 };
329 
330 BEAST_DEFINE_TESTSUITE(AccountOffers, app, ripple);
331 
332 } // namespace test
333 } // namespace ripple
ripple::test::jtx::XRP
const XRP_t XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
ripple::test::jtx::checkArraySize
bool checkArraySize(Json::Value const &val, unsigned int size)
Definition: TestHelpers.cpp:48
Json::Value::isString
bool isString() const
Definition: json_value.cpp:1009
std::string::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
Json::Value::toStyledString
std::string toStyledString() const
Definition: json_value.cpp:1039
ripple::test::jtx::envconfig
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:49
ripple::test::jtx::Env::trust
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:259
ripple::sfExpiration
const SF_UINT32 sfExpiration
ripple::test::jtx::no_admin
std::unique_ptr< Config > no_admin(std::unique_ptr< Config >)
adjust config so no admin ports are enabled
Definition: envconfig.cpp:82
ripple::test::AccountOffers_test
Definition: AccountOffers_test.cpp:27
ripple::test::AccountOffers_test::testSequential
void testSequential(bool asAdmin)
Definition: AccountOffers_test.cpp:83
ripple::test::AccountOffers_test::checkMarker
static bool checkMarker(Json::Value const &val)
Definition: AccountOffers_test.cpp:32
Json::Value::isMember
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:932
ripple::test::AccountOffers_test::testBadInput
void testBadInput()
Definition: AccountOffers_test.cpp:217
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::test::jtx::Env::fund
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:228
ripple::test::jtx::Account
Immutable cryptographic account descriptor.
Definition: Account.h:37
ripple::test::AccountOffers_test::run
void run() override
Definition: AccountOffers_test.cpp:321
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:699
ripple::test::AccountOffers_test::testNonAdminMinLimit
void testNonAdminMinLimit()
Definition: AccountOffers_test.cpp:39
Json::Value
Represents a JSON value.
Definition: json_value.h:145
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)