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