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
22#include <xrpl/protocol/jss.h>
23
24namespace ripple {
25namespace test {
26
28{
29public:
30 // test helper
31 static bool
33 {
34 return val.isMember(jss::marker) && val[jss::marker].isString() &&
35 val[jss::marker].asString().size() > 0;
36 }
37
38 void
40 {
41 testcase("Non-Admin Min Limit");
42
43 using namespace jtx;
44 Env env{*this, envconfig(no_admin)};
45 Account const gw("G1");
46 auto const USD_gw = gw["USD"];
47 Account const bob("bob");
48 auto const USD_bob = bob["USD"];
49
50 env.fund(XRP(10000), gw, bob);
51 env.trust(USD_gw(1000), bob);
52
53 // this is to provide some USD from gw in the
54 // bob account so that it can rightly
55 // make offers that give those USDs
56 env(pay(gw, bob, USD_gw(10)));
57 unsigned const offer_count = 12u;
58 for (auto i = 0u; i < offer_count; i++)
59 {
60 Json::Value jvo = offer(bob, XRP(100 + i), USD_gw(1));
61 jvo[sfExpiration.fieldName] = 10000000u;
62 env(jvo);
63 }
64
65 // make non-limited RPC call
66 auto const jro_nl =
67 env.rpc("account_offers", bob.human())[jss::result][jss::offers];
68 BEAST_EXPECT(checkArraySize(jro_nl, offer_count));
69
70 // now make a low-limit query, should get "corrected"
71 // to a min of 10 results with a marker set since there
72 // are more than 10 total
73 Json::Value jvParams;
74 jvParams[jss::account] = bob.human();
75 jvParams[jss::limit] = 1u;
76 auto const jrr_l = env.rpc(
77 "json", "account_offers", jvParams.toStyledString())[jss::result];
78 auto const& jro_l = jrr_l[jss::offers];
79 BEAST_EXPECT(checkMarker(jrr_l));
80 // 9u is the expected size, since one account object is a trustline
81 BEAST_EXPECT(checkArraySize(jro_l, 9u));
82 }
83
84 void
85 testSequential(bool asAdmin)
86 {
88 std::string("Sequential - ") + (asAdmin ? "admin" : "non-admin"));
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 jvParams[jss::limit] = 10u;
177 auto const jrr_l_3 = env.rpc(
178 "json",
179 "account_offers",
180 jvParams.toStyledString())[jss::result];
181 auto const& jro_l_3 = jrr_l_3[jss::offers];
182 BEAST_EXPECT(!jrr_l_3.isMember(jss::marker));
183 BEAST_EXPECT(checkArraySize(jro_l_3, 1u));
184 BEAST_EXPECT(jroOuter[2u] == jro_l_3[0u]);
185 }
186 else
187 {
188 BEAST_EXPECT(jroOuter == jro_l_1);
189 }
190 }
191
192 {
193 // now make a limit (= 0) query for the same data
194 // since we operate on the admin port, the limit
195 // value of 0 is not adjusted into tuned ranges for admin requests
196 // so we literally get 0 elements in that case. For non-admin
197 // requests, we get limit defaults applied thus all our results
198 // come back (we are below the min results limit)
199 Json::Value jvParams;
200 jvParams[jss::account] = bob.human();
201 jvParams[jss::limit] = 0u;
202 auto const jrr = env.rpc(
203 "json",
204 "account_offers",
205 jvParams.toStyledString())[jss::result];
206 auto const& jro = jrr[jss::offers];
207 if (asAdmin)
208 {
209 // limit == 0 is invalid
210 BEAST_EXPECT(jrr.isMember(jss::error_message));
211 }
212 else
213 {
214 // Call should enforce min limit of 10
215 BEAST_EXPECT(checkArraySize(jro, 3u));
216 BEAST_EXPECT(!jrr.isMember(jss::marker));
217 }
218 }
219 }
220
221 void
223 {
224 testcase("Bad input");
225
226 using namespace jtx;
227 Env env(*this);
228 Account const gw("G1");
229 auto const USD_gw = gw["USD"];
230 Account const bob("bob");
231 auto const USD_bob = bob["USD"];
232
233 env.fund(XRP(10000), gw, bob);
234 env.trust(USD_gw(1000), bob);
235
236 {
237 // no account field
238 auto const jrr = env.rpc("account_offers");
239 BEAST_EXPECT(jrr[jss::error] == "badSyntax");
240 BEAST_EXPECT(jrr[jss::status] == "error");
241 BEAST_EXPECT(jrr[jss::error_message] == "Syntax error.");
242 }
243
244 {
245 // test account non-string
246 auto testInvalidAccountParam = [&](auto const& param) {
247 Json::Value params;
248 params[jss::account] = param;
249 auto jrr = env.rpc(
250 "json", "account_offers", to_string(params))[jss::result];
251 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
252 BEAST_EXPECT(
253 jrr[jss::error_message] == "Invalid field 'account'.");
254 };
255
256 testInvalidAccountParam(1);
257 testInvalidAccountParam(1.1);
258 testInvalidAccountParam(true);
259 testInvalidAccountParam(Json::Value(Json::nullValue));
260 testInvalidAccountParam(Json::Value(Json::objectValue));
261 testInvalidAccountParam(Json::Value(Json::arrayValue));
262 }
263
264 {
265 // empty string account
266 Json::Value jvParams;
267 jvParams[jss::account] = "";
268 auto const jrr = env.rpc(
269 "json",
270 "account_offers",
271 jvParams.toStyledString())[jss::result];
272 BEAST_EXPECT(jrr[jss::error] == "actMalformed");
273 BEAST_EXPECT(jrr[jss::status] == "error");
274 BEAST_EXPECT(jrr[jss::error_message] == "Account malformed.");
275 }
276
277 {
278 // bogus account value
279 auto const jrr = env.rpc(
280 "account_offers", Account("bogus").human())[jss::result];
281 BEAST_EXPECT(jrr[jss::error] == "actNotFound");
282 BEAST_EXPECT(jrr[jss::status] == "error");
283 BEAST_EXPECT(jrr[jss::error_message] == "Account not found.");
284 }
285
286 {
287 // bad limit
288 Json::Value jvParams;
289 jvParams[jss::account] = bob.human();
290 jvParams[jss::limit] = "0"; // NOT an integer
291 auto const jrr = env.rpc(
292 "json",
293 "account_offers",
294 jvParams.toStyledString())[jss::result];
295 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
296 BEAST_EXPECT(jrr[jss::status] == "error");
297 BEAST_EXPECT(
298 jrr[jss::error_message] ==
299 "Invalid field 'limit', not unsigned integer.");
300 }
301
302 {
303 // invalid marker
304 Json::Value jvParams;
305 jvParams[jss::account] = bob.human();
306 jvParams[jss::marker] = "NOT_A_MARKER";
307 auto const jrr = env.rpc(
308 "json",
309 "account_offers",
310 jvParams.toStyledString())[jss::result];
311 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
312 BEAST_EXPECT(jrr[jss::status] == "error");
313 BEAST_EXPECTS(
314 jrr[jss::error_message] == "Invalid field 'marker'.",
315 jrr.toStyledString());
316 }
317
318 {
319 // invalid marker - not a string
320 Json::Value jvParams;
321 jvParams[jss::account] = bob.human();
322 jvParams[jss::marker] = 1;
323 auto const jrr = env.rpc(
324 "json",
325 "account_offers",
326 jvParams.toStyledString())[jss::result];
327 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
328 BEAST_EXPECT(jrr[jss::status] == "error");
329 BEAST_EXPECT(
330 jrr[jss::error_message] ==
331 "Invalid field 'marker', not string.");
332 }
333
334 {
335 // ask for a bad ledger index
336 Json::Value jvParams;
337 jvParams[jss::account] = bob.human();
338 jvParams[jss::ledger_index] = 10u;
339 auto const jrr = env.rpc(
340 "json",
341 "account_offers",
342 jvParams.toStyledString())[jss::result];
343 BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
344 BEAST_EXPECT(jrr[jss::status] == "error");
345 BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
346 }
347 }
348
349 void
350 run() override
351 {
352 testSequential(true);
353 testSequential(false);
354 testBadInput();
356 }
357};
358
359BEAST_DEFINE_TESTSUITE(AccountOffers, rpc, ripple);
360
361} // namespace test
362} // namespace ripple
Represents a JSON value.
Definition json_value.h:149
std::string toStyledString() const
bool isString() const
std::string asString() const
Returns the unquoted string value.
bool isMember(char const *key) const
Return true if the object has a member named key.
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:118
A transaction testing environment.
Definition Env.h:121
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:320
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:791
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:289
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:38
@ arrayValue
array value (ordered list)
Definition json_value.h:44
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:45
bool checkArraySize(Json::Value const &val, unsigned int size)
std::unique_ptr< Config > no_admin(std::unique_ptr< Config >)
adjust config so no admin ports are enabled
Definition envconfig.cpp:76
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:30
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:29
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:111
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
T size(T... args)