rippled
Loading...
Searching...
No Matches
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 <test/jtx.h>
21#include <xrpl/protocol/jss.h>
22
23namespace ripple {
24namespace test {
25
27{
28public:
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 testcase("Non-Admin Min Limit");
41
42 using namespace jtx;
43 Env env{*this, envconfig(no_admin)};
44 Account const gw("G1");
45 auto const USD_gw = gw["USD"];
46 Account const bob("bob");
47 auto const USD_bob = bob["USD"];
48
49 env.fund(XRP(10000), gw, bob);
50 env.trust(USD_gw(1000), bob);
51
52 // this is to provide some USD from gw in the
53 // bob account so that it can rightly
54 // make offers that give those USDs
55 env(pay(gw, bob, USD_gw(10)));
56 unsigned const offer_count = 12u;
57 for (auto i = 0u; i < offer_count; i++)
58 {
59 Json::Value jvo = offer(bob, XRP(100 + i), USD_gw(1));
60 jvo[sfExpiration.fieldName] = 10000000u;
61 env(jvo);
62 }
63
64 // make non-limited RPC call
65 auto const jro_nl =
66 env.rpc("account_offers", bob.human())[jss::result][jss::offers];
67 BEAST_EXPECT(checkArraySize(jro_nl, offer_count));
68
69 // now make a low-limit query, should get "corrected"
70 // to a min of 10 results with a marker set since there
71 // are more than 10 total
72 Json::Value jvParams;
73 jvParams[jss::account] = bob.human();
74 jvParams[jss::limit] = 1u;
75 auto const jrr_l = env.rpc(
76 "json", "account_offers", jvParams.toStyledString())[jss::result];
77 auto const& jro_l = jrr_l[jss::offers];
78 BEAST_EXPECT(checkMarker(jrr_l));
79 // 9u is the expected size, since one account object is a trustline
80 BEAST_EXPECT(checkArraySize(jro_l, 9u));
81 }
82
83 void
84 testSequential(bool asAdmin)
85 {
87 std::string("Sequential - ") + (asAdmin ? "admin" : "non-admin"));
88
89 using namespace jtx;
90 Env env{*this, asAdmin ? envconfig() : envconfig(no_admin)};
91 Account const gw("G1");
92 auto const USD_gw = gw["USD"];
93 Account const bob("bob");
94 auto const USD_bob = bob["USD"];
95
96 env.fund(XRP(10000), gw, bob);
97 env.trust(USD_gw(1000), bob);
98
99 // this is to provide some USD from gw in the
100 // bob account so that it can rightly
101 // make offers that give those USDs
102 env(pay(gw, bob, USD_gw(10)));
103
104 env(offer(bob, XRP(100), USD_bob(1)));
105 env(offer(bob, XRP(200), USD_gw(2)));
106 env(offer(bob, XRP(30), USD_gw(6)));
107
108 // make the RPC call
109 auto const jroOuter =
110 env.rpc("account_offers", bob.human())[jss::result][jss::offers];
111 if (BEAST_EXPECT(checkArraySize(jroOuter, 3u)))
112 {
113 // Note that the returned offers are sorted by index, not by
114 // order of insertion or by sequence number. There is no
115 // guarantee that their order will not change in the future
116 // if the sequence numbers or the account IDs change.
117 BEAST_EXPECT(jroOuter[0u][jss::quality] == "100000000");
118 BEAST_EXPECT(jroOuter[0u][jss::taker_gets][jss::currency] == "USD");
119 BEAST_EXPECT(
120 jroOuter[0u][jss::taker_gets][jss::issuer] == gw.human());
121 BEAST_EXPECT(jroOuter[0u][jss::taker_gets][jss::value] == "2");
122 BEAST_EXPECT(jroOuter[0u][jss::taker_pays] == "200000000");
123
124 BEAST_EXPECT(jroOuter[1u][jss::quality] == "100000000");
125 BEAST_EXPECT(jroOuter[1u][jss::taker_gets][jss::currency] == "USD");
126 BEAST_EXPECT(
127 jroOuter[1u][jss::taker_gets][jss::issuer] == bob.human());
128 BEAST_EXPECT(jroOuter[1u][jss::taker_gets][jss::value] == "1");
129 BEAST_EXPECT(jroOuter[1u][jss::taker_pays] == "100000000");
130
131 BEAST_EXPECT(jroOuter[2u][jss::quality] == "5000000");
132 BEAST_EXPECT(jroOuter[2u][jss::taker_gets][jss::currency] == "USD");
133 BEAST_EXPECT(
134 jroOuter[2u][jss::taker_gets][jss::issuer] == gw.human());
135 BEAST_EXPECT(jroOuter[2u][jss::taker_gets][jss::value] == "6");
136 BEAST_EXPECT(jroOuter[2u][jss::taker_pays] == "30000000");
137 }
138
139 {
140 // now make a limit (= 1) query for the same data
141 Json::Value jvParams;
142 jvParams[jss::account] = bob.human();
143 jvParams[jss::limit] = 1u;
144 auto const jrr_l_1 = env.rpc(
145 "json",
146 "account_offers",
147 jvParams.toStyledString())[jss::result];
148 auto const& jro_l_1 = jrr_l_1[jss::offers];
149 // there is a difference in the validation of the limit param
150 // between admin and non-admin requests. with admin requests, the
151 // limit parameter is NOT subject to sane defaults, but with a
152 // non-admin there are pre-configured limit ranges applied. That's
153 // why we have different BEAST_EXPECT()s here for the two scenarios
154 BEAST_EXPECT(checkArraySize(jro_l_1, asAdmin ? 1u : 3u));
155 BEAST_EXPECT(
156 asAdmin ? checkMarker(jrr_l_1)
157 : (!jrr_l_1.isMember(jss::marker)));
158 if (asAdmin)
159 {
160 BEAST_EXPECT(jroOuter[0u] == jro_l_1[0u]);
161
162 // second item...with previous marker passed
163 jvParams[jss::marker] = jrr_l_1[jss::marker];
164 auto const jrr_l_2 = env.rpc(
165 "json",
166 "account_offers",
167 jvParams.toStyledString())[jss::result];
168 auto const& jro_l_2 = jrr_l_2[jss::offers];
169 BEAST_EXPECT(checkMarker(jrr_l_2));
170 BEAST_EXPECT(checkArraySize(jro_l_2, 1u));
171 BEAST_EXPECT(jroOuter[1u] == jro_l_2[0u]);
172
173 // last item...with previous marker passed
174 jvParams[jss::marker] = jrr_l_2[jss::marker];
175 jvParams[jss::limit] = 10u;
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 if (asAdmin)
207 {
208 // limit == 0 is invalid
209 BEAST_EXPECT(jrr.isMember(jss::error_message));
210 }
211 else
212 {
213 // Call should enforce min limit of 10
214 BEAST_EXPECT(checkArraySize(jro, 3u));
215 BEAST_EXPECT(!jrr.isMember(jss::marker));
216 }
217 }
218 }
219
220 void
222 {
223 testcase("Bad input");
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 // test account non-string
245 auto testInvalidAccountParam = [&](auto const& param) {
246 Json::Value params;
247 params[jss::account] = param;
248 auto jrr = env.rpc(
249 "json", "account_offers", to_string(params))[jss::result];
250 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
251 BEAST_EXPECT(
252 jrr[jss::error_message] == "Invalid field 'account'.");
253 };
254
255 testInvalidAccountParam(1);
256 testInvalidAccountParam(1.1);
257 testInvalidAccountParam(true);
258 testInvalidAccountParam(Json::Value(Json::nullValue));
259 testInvalidAccountParam(Json::Value(Json::objectValue));
260 testInvalidAccountParam(Json::Value(Json::arrayValue));
261 }
262
263 {
264 // empty string account
265 Json::Value jvParams;
266 jvParams[jss::account] = "";
267 auto const jrr = env.rpc(
268 "json",
269 "account_offers",
270 jvParams.toStyledString())[jss::result];
271 BEAST_EXPECT(jrr[jss::error] == "actMalformed");
272 BEAST_EXPECT(jrr[jss::status] == "error");
273 BEAST_EXPECT(jrr[jss::error_message] == "Account malformed.");
274 }
275
276 {
277 // bogus account value
278 auto const jrr = env.rpc(
279 "account_offers", Account("bogus").human())[jss::result];
280 BEAST_EXPECT(jrr[jss::error] == "actNotFound");
281 BEAST_EXPECT(jrr[jss::status] == "error");
282 BEAST_EXPECT(jrr[jss::error_message] == "Account not found.");
283 }
284
285 {
286 // bad limit
287 Json::Value jvParams;
288 jvParams[jss::account] = bob.human();
289 jvParams[jss::limit] = "0"; // NOT an integer
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 'limit', not unsigned integer.");
299 }
300
301 {
302 // invalid marker
303 Json::Value jvParams;
304 jvParams[jss::account] = bob.human();
305 jvParams[jss::marker] = "NOT_A_MARKER";
306 auto const jrr = env.rpc(
307 "json",
308 "account_offers",
309 jvParams.toStyledString())[jss::result];
310 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
311 BEAST_EXPECT(jrr[jss::status] == "error");
312 BEAST_EXPECTS(
313 jrr[jss::error_message] == "Invalid field 'marker'.",
314 jrr.toStyledString());
315 }
316
317 {
318 // invalid marker - not a string
319 Json::Value jvParams;
320 jvParams[jss::account] = bob.human();
321 jvParams[jss::marker] = 1;
322 auto const jrr = env.rpc(
323 "json",
324 "account_offers",
325 jvParams.toStyledString())[jss::result];
326 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
327 BEAST_EXPECT(jrr[jss::status] == "error");
328 BEAST_EXPECT(
329 jrr[jss::error_message] ==
330 "Invalid field 'marker', not string.");
331 }
332
333 {
334 // ask for a bad ledger index
335 Json::Value jvParams;
336 jvParams[jss::account] = bob.human();
337 jvParams[jss::ledger_index] = 10u;
338 auto const jrr = env.rpc(
339 "json",
340 "account_offers",
341 jvParams.toStyledString())[jss::result];
342 BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
343 BEAST_EXPECT(jrr[jss::status] == "error");
344 BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
345 }
346 }
347
348 void
349 run() override
350 {
351 testSequential(true);
352 testSequential(false);
353 testBadInput();
355 }
356};
357
358BEAST_DEFINE_TESTSUITE(AccountOffers, rpc, ripple);
359
360} // namespace test
361} // namespace ripple
Represents a JSON value.
Definition: json_value.h:148
std::string toStyledString() const
bool isString() const
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:475
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:949
A testsuite class.
Definition: suite.h:55
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:155
void run() override
Runs the suite.
static bool checkMarker(Json::Value const &val)
Immutable cryptographic account descriptor.
Definition: Account.h:39
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:114
A transaction testing environment.
Definition: Env.h:118
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:262
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:765
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:231
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition: rpc.h:35
@ nullValue
'null' value
Definition: json_value.h:37
@ arrayValue
array value (ordered list)
Definition: json_value.h:43
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:44
bool checkArraySize(Json::Value const &val, unsigned int size)
Definition: TestHelpers.cpp:47
std::unique_ptr< Config > no_admin(std::unique_ptr< Config >)
adjust config so no admin ports are enabled
Definition: envconfig.cpp:75
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:29
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:54
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition: offer.cpp:28
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:104
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
T size(T... args)