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