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 checkArraySize(Json::Value const& val, unsigned int size)
31  {
32  return val.isArray() &&
33  val.size() == size;
34  }
35 
36  // test helper
37  static bool checkMarker(Json::Value const& val)
38  {
39  return val.isMember(jss::marker) &&
40  val[jss::marker].isString() &&
41  val[jss::marker].asString().size() > 0;
42  }
43 
45  {
46  using namespace jtx;
47  Env env {*this, envconfig(no_admin)};
48  Account const gw ("G1");
49  auto const USD_gw = gw["USD"];
50  Account const bob ("bob");
51  auto const USD_bob = bob["USD"];
52 
53  env.fund(XRP(10000), gw, bob);
54  env.trust(USD_gw(1000), bob);
55 
56  // this is to provide some USD from gw in the
57  // bob account so that it can rightly
58  // make offers that give those USDs
59  env (pay (gw, bob, USD_gw (10)));
60  unsigned const offer_count = 12u;
61  for (auto i = 0u; i < offer_count; i++)
62  {
63  Json::Value jvo = offer(bob, XRP(100 + i), USD_gw(1));
64  jvo[sfExpiration.fieldName] = 10000000u;
65  env(jvo);
66  }
67 
68  // make non-limited RPC call
69  auto const jro_nl = env.rpc("account_offers", bob.human())[jss::result][jss::offers];
70  BEAST_EXPECT(checkArraySize(jro_nl, offer_count));
71 
72  // now make a low-limit query, should get "corrected"
73  // to a min of 10 results with a marker set since there
74  // are more than 10 total
75  Json::Value jvParams;
76  jvParams[jss::account] = bob.human();
77  jvParams[jss::limit] = 1u;
78  auto const jrr_l = env.rpc ("json", "account_offers", jvParams.toStyledString())[jss::result];
79  auto const& jro_l = jrr_l[jss::offers];
80  BEAST_EXPECT(checkMarker(jrr_l));
81  BEAST_EXPECT(checkArraySize(jro_l, 10u));
82  }
83 
84  void testSequential(bool asAdmin)
85  {
86  using namespace jtx;
87  Env env {*this, asAdmin ? envconfig() : envconfig(no_admin)};
88  Account const gw ("G1");
89  auto const USD_gw = gw["USD"];
90  Account const bob ("bob");
91  auto const USD_bob = bob["USD"];
92 
93  env.fund(XRP(10000), gw, bob);
94  env.trust(USD_gw(1000), bob);
95 
96  // this is to provide some USD from gw in the
97  // bob account so that it can rightly
98  // make offers that give those USDs
99  env (pay (gw, bob, USD_gw (10)));
100 
101  env(offer(bob, XRP(100), USD_bob(1)));
102  env(offer(bob, XRP(200), USD_gw(2)));
103  env(offer(bob, XRP(30), USD_gw(6)));
104 
105  // make the RPC call
106  auto const jroOuter = 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(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(jroOuter[1u][jss::taker_gets][jss::issuer] == bob.human());
122  BEAST_EXPECT(jroOuter[1u][jss::taker_gets][jss::value] == "1");
123  BEAST_EXPECT(jroOuter[1u][jss::taker_pays] == "100000000");
124 
125  BEAST_EXPECT(jroOuter[2u][jss::quality] == "5000000");
126  BEAST_EXPECT(jroOuter[2u][jss::taker_gets][jss::currency] == "USD");
127  BEAST_EXPECT(jroOuter[2u][jss::taker_gets][jss::issuer] == gw.human());
128  BEAST_EXPECT(jroOuter[2u][jss::taker_gets][jss::value] == "6");
129  BEAST_EXPECT(jroOuter[2u][jss::taker_pays] == "30000000");
130  }
131 
132  {
133  // now make a limit (= 1) query for the same data
134  Json::Value jvParams;
135  jvParams[jss::account] = bob.human();
136  jvParams[jss::limit] = 1u;
137  auto const jrr_l_1 = env.rpc ("json", "account_offers", jvParams.toStyledString())[jss::result];
138  auto const& jro_l_1 = jrr_l_1[jss::offers];
139  // there is a difference in the validation of the limit param
140  // between admin and non-admin requests. with admin requests, the
141  // limit parameter is NOT subject to sane defaults, but with a
142  // non-admin there are pre-configured limit ranges applied. That's
143  // why we have different BEAST_EXPECT()s here for the two scenarios
144  BEAST_EXPECT(checkArraySize(jro_l_1, asAdmin ? 1u : 3u));
145  BEAST_EXPECT(asAdmin ? checkMarker(jrr_l_1) : (! jrr_l_1.isMember(jss::marker)));
146  if (asAdmin)
147  {
148  BEAST_EXPECT(jroOuter[0u] == jro_l_1[0u]);
149 
150  // second item...with previous marker passed
151  jvParams[jss::marker] = jrr_l_1[jss::marker];
152  auto const jrr_l_2 = env.rpc ("json", "account_offers", jvParams.toStyledString())[jss::result];
153  auto const& jro_l_2 = jrr_l_2[jss::offers];
154  BEAST_EXPECT(checkMarker(jrr_l_2));
155  BEAST_EXPECT(checkArraySize(jro_l_2, 1u));
156  BEAST_EXPECT(jroOuter[1u] == jro_l_2[0u]);
157 
158  // last item...with previous marker passed
159  jvParams[jss::marker] = jrr_l_2[jss::marker];
160  auto const jrr_l_3 = env.rpc ("json", "account_offers", jvParams.toStyledString())[jss::result];
161  auto const& jro_l_3 = jrr_l_3[jss::offers];
162  BEAST_EXPECT(! jrr_l_3.isMember(jss::marker));
163  BEAST_EXPECT(checkArraySize(jro_l_3, 1u));
164  BEAST_EXPECT(jroOuter[2u] == jro_l_3[0u]);
165  }
166  else
167  {
168  BEAST_EXPECT(jroOuter == jro_l_1);
169  }
170  }
171 
172  {
173  // now make a limit (= 0) query for the same data
174  // since we operate on the admin port, the limit
175  // value of 0 is not adjusted into tuned ranges for admin requests so
176  // we literally get 0 elements in that case. For non-admin
177  // requests, we get limit defaults applied thus all our results
178  // come back (we are below the min results limit)
179  Json::Value jvParams;
180  jvParams[jss::account] = bob.human();
181  jvParams[jss::limit] = 0u;
182  auto const jrr = env.rpc ("json", "account_offers", jvParams.toStyledString())[jss::result];
183  auto const& jro = jrr[jss::offers];
184  BEAST_EXPECT(checkArraySize(jro, asAdmin ? 0u : 3u));
185  BEAST_EXPECT(asAdmin ? checkMarker(jrr) : (! jrr.isMember(jss::marker)));
186  }
187 
188  }
189 
191  {
192  using namespace jtx;
193  Env env(*this);
194  Account const gw ("G1");
195  auto const USD_gw = gw["USD"];
196  Account const bob ("bob");
197  auto const USD_bob = bob["USD"];
198 
199  env.fund(XRP(10000), gw, bob);
200  env.trust(USD_gw(1000), bob);
201 
202  {
203  // no account field
204  auto const jrr = env.rpc ("account_offers");
205  BEAST_EXPECT(jrr[jss::error] == "badSyntax");
206  BEAST_EXPECT(jrr[jss::status] == "error");
207  BEAST_EXPECT(jrr[jss::error_message] == "Syntax error.");
208  }
209 
210  {
211  // empty string account
212  Json::Value jvParams;
213  jvParams[jss::account] = "";
214  auto const jrr = env.rpc ("json", "account_offers", jvParams.toStyledString())[jss::result];
215  BEAST_EXPECT(jrr[jss::error] == "badSeed");
216  BEAST_EXPECT(jrr[jss::status] == "error");
217  BEAST_EXPECT(jrr[jss::error_message] == "Disallowed seed.");
218  }
219 
220  {
221  // bogus account value
222  auto const jrr = env.rpc ("account_offers", "rNOT_AN_ACCOUNT")[jss::result];
223  BEAST_EXPECT(jrr[jss::error] == "actNotFound");
224  BEAST_EXPECT(jrr[jss::status] == "error");
225  BEAST_EXPECT(jrr[jss::error_message] == "Account not found.");
226  }
227 
228  {
229  // bad limit
230  Json::Value jvParams;
231  jvParams[jss::account] = bob.human();
232  jvParams[jss::limit] = "0"; // NOT an integer
233  auto const jrr = env.rpc ("json", "account_offers", jvParams.toStyledString())[jss::result];
234  BEAST_EXPECT(jrr[jss::error] == "invalidParams");
235  BEAST_EXPECT(jrr[jss::status] == "error");
236  BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'limit', not unsigned integer.");
237  }
238 
239  {
240  // invalid marker
241  Json::Value jvParams;
242  jvParams[jss::account] = bob.human();
243  jvParams[jss::marker] = "NOT_A_MARKER";
244  auto const jrr = env.rpc ("json", "account_offers", jvParams.toStyledString())[jss::result];
245  BEAST_EXPECT(jrr[jss::error] == "invalidParams");
246  BEAST_EXPECT(jrr[jss::status] == "error");
247  BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
248  }
249 
250  {
251  // invalid marker - not a string
252  Json::Value jvParams;
253  jvParams[jss::account] = bob.human();
254  jvParams[jss::marker] = 1;
255  auto const jrr = env.rpc ("json", "account_offers", jvParams.toStyledString())[jss::result];
256  BEAST_EXPECT(jrr[jss::error] == "invalidParams");
257  BEAST_EXPECT(jrr[jss::status] == "error");
258  BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'marker', not string.");
259  }
260 
261  {
262  // ask for a bad ledger index
263  Json::Value jvParams;
264  jvParams[jss::account] = bob.human();
265  jvParams[jss::ledger_index] = 10u;
266  auto const jrr = env.rpc ("json", "account_offers", jvParams.toStyledString())[jss::result];
267  BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
268  BEAST_EXPECT(jrr[jss::status] == "error");
269  BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
270  }
271 
272  }
273 
274  void run() override
275  {
276  testSequential(true);
277  testSequential(false);
278  testBadInput();
280  }
281 
282 };
283 
284 BEAST_DEFINE_TESTSUITE(AccountOffers,app,ripple);
285 
286 }
287 }
288 
ripple::test::jtx::XRP
const XRP_t XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:109
ripple::test::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(AccountDelete, app, ripple)
Json::Value::isString
bool isString() const
Definition: json_value.cpp:1049
std::string::size
T size(T... args)
ripple::SField::fieldName
const std::string fieldName
Definition: SField.h:136
ripple::test::jtx::Account::human
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:104
Json::Value::toStyledString
std::string toStyledString() const
Definition: json_value.cpp:1081
ripple::test::jtx::envconfig
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:52
ripple::test::jtx::offer
Json::Value offer(Account const &account, STAmount const &in, STAmount const &out, std::uint32_t flags)
Create an offer.
Definition: offer.cpp:28
ripple::test::jtx::Env::trust
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:245
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:69
ripple::test::AccountOffers_test
Definition: AccountOffers_test.cpp:26
ripple::test::AccountOffers_test::testSequential
void testSequential(bool asAdmin)
Definition: AccountOffers_test.cpp:84
ripple::test::AccountOffers_test::checkMarker
static bool checkMarker(Json::Value const &val)
Definition: AccountOffers_test.cpp:37
Json::Value::size
UInt size() const
Number of values in array or object.
Definition: json_value.cpp:720
ripple::test::AccountOffers_test::checkArraySize
static bool checkArraySize(Json::Value const &val, unsigned int size)
Definition: AccountOffers_test.cpp:30
Json::Value::isMember
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:961
ripple::sfExpiration
const SF_U32 sfExpiration(access, STI_UINT32, 10, "Expiration")
Definition: SField.h:346
ripple::test::AccountOffers_test::testBadInput
void testBadInput()
Definition: AccountOffers_test.cpp:190
Json::Value::isArray
bool isArray() const
Definition: json_value.cpp:1056
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:214
ripple::test::jtx::Account
Immutable cryptographic account descriptor.
Definition: Account.h:37
ripple::test::AccountOffers_test::run
void run() override
Definition: AccountOffers_test.cpp:274
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:117
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:688
ripple::test::AccountOffers_test::testNonAdminMinLimit
void testNonAdminMinLimit()
Definition: AccountOffers_test.cpp:44
Json::Value
Represents a JSON value.
Definition: json_value.h:141
Json::Value::asString
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:482