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