rippled
Loading...
Searching...
No Matches
DepositAuthorized_test.cpp
1#include <test/jtx.h>
2
3#include <xrpl/protocol/jss.h>
4
5namespace xrpl {
6namespace test {
7
9{
10public:
11 // Helper function that builds arguments for a deposit_authorized command.
12 static Json::Value
14 jtx::Account const& source,
15 jtx::Account const& dest,
16 std::string const& ledger = "",
17 std::vector<std::string> const& credentials = {})
18 {
20 args[jss::source_account] = source.human();
21 args[jss::destination_account] = dest.human();
22 if (!ledger.empty())
23 args[jss::ledger_index] = ledger;
24
25 if (!credentials.empty())
26 {
27 auto& arr(args[jss::credentials] = Json::arrayValue);
28 for (auto const& s : credentials)
29 arr.append(s);
30 }
31
32 return args;
33 }
34
35 // Helper function that verifies a deposit_authorized request was
36 // successful and returned the expected value.
37 void
39 {
40 Json::Value const& results{result[jss::result]};
41 BEAST_EXPECT(results[jss::deposit_authorized] == authorized);
42 BEAST_EXPECT(results[jss::status] == jss::success);
43 }
44
45 // Test a variety of non-malformed cases.
46 void
48 {
49 testcase("Valid");
50 using namespace jtx;
51 Account const alice{"alice"};
52 Account const becky{"becky"};
53 Account const carol{"carol"};
54
55 Env env(*this);
56 env.fund(XRP(1000), alice, becky, carol);
57 env.close();
58
59 // becky is authorized to deposit to herself.
61 env.rpc(
62 "json",
63 "deposit_authorized",
64 depositAuthArgs(becky, becky, "validated").toStyledString()),
65 true);
66
67 // alice should currently be authorized to deposit to becky.
69 env.rpc(
70 "json",
71 "deposit_authorized",
72 depositAuthArgs(alice, becky, "validated").toStyledString()),
73 true);
74
75 // becky sets the DepositAuth flag in the current ledger.
76 env(fset(becky, asfDepositAuth));
77
78 // alice is no longer authorized to deposit to becky in current ledger.
80 env.rpc("json", "deposit_authorized", depositAuthArgs(alice, becky).toStyledString()),
81 false);
82 env.close();
83
84 // becky is still authorized to deposit to herself.
86 env.rpc(
87 "json",
88 "deposit_authorized",
89 depositAuthArgs(becky, becky, "validated").toStyledString()),
90 true);
91
92 // It's not a reciprocal arrangement. becky can deposit to alice.
94 env.rpc(
95 "json",
96 "deposit_authorized",
97 depositAuthArgs(becky, alice, "current").toStyledString()),
98 true);
99
100 // becky creates a deposit authorization for alice.
101 env(deposit::auth(becky, alice));
102 env.close();
103
104 // alice is now authorized to deposit to becky.
106 env.rpc(
107 "json",
108 "deposit_authorized",
109 depositAuthArgs(alice, becky, "closed").toStyledString()),
110 true);
111
112 // carol is still not authorized to deposit to becky.
114 env.rpc("json", "deposit_authorized", depositAuthArgs(carol, becky).toStyledString()),
115 false);
116
117 // becky clears the DepositAuth flag so carol becomes authorized.
118 env(fclear(becky, asfDepositAuth));
119 env.close();
120
122 env.rpc("json", "deposit_authorized", depositAuthArgs(carol, becky).toStyledString()),
123 true);
124
125 // alice is still authorized to deposit to becky.
127 env.rpc("json", "deposit_authorized", depositAuthArgs(alice, becky).toStyledString()),
128 true);
129 }
130
131 // Test malformed cases.
132 void
134 {
135 testcase("Errors");
136 using namespace jtx;
137 Account const alice{"alice"};
138 Account const becky{"becky"};
139
140 // Lambda that checks the (error) result of deposit_authorized.
141 auto verifyErr = [this](
142 Json::Value const& result, char const* error, char const* errorMsg) {
143 BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
144 BEAST_EXPECT(result[jss::result][jss::error] == error);
145 BEAST_EXPECT(result[jss::result][jss::error_message] == errorMsg);
146 };
147
148 Env env(*this);
149 {
150 // Missing source_account field.
151 Json::Value args{depositAuthArgs(alice, becky)};
152 args.removeMember(jss::source_account);
153 Json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
154 verifyErr(result, "invalidParams", "Missing field 'source_account'.");
155 }
156 {
157 // Non-string source_account field.
158 Json::Value args{depositAuthArgs(alice, becky)};
159 args[jss::source_account] = 7.3;
160 Json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
161 verifyErr(result, "invalidParams", "Invalid field 'source_account', not a string.");
162 }
163 {
164 // Corrupt source_account field.
165 Json::Value args{depositAuthArgs(alice, becky)};
166 args[jss::source_account] = "rG1QQv2nh2gr7RCZ!P8YYcBUKCCN633jCn";
167 Json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
168 verifyErr(result, "actMalformed", "Account malformed.");
169 }
170 {
171 // Missing destination_account field.
172 Json::Value args{depositAuthArgs(alice, becky)};
173 args.removeMember(jss::destination_account);
174 Json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
175 verifyErr(result, "invalidParams", "Missing field 'destination_account'.");
176 }
177 {
178 // Non-string destination_account field.
179 Json::Value args{depositAuthArgs(alice, becky)};
180 args[jss::destination_account] = 7.3;
181 Json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
182 verifyErr(
183 result, "invalidParams", "Invalid field 'destination_account', not a string.");
184 }
185 {
186 // Corrupt destination_account field.
187 Json::Value args{depositAuthArgs(alice, becky)};
188 args[jss::destination_account] = "rP6P9ypfAmc!pw8SZHNwM4nvZHFXDraQas";
189 Json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
190 verifyErr(result, "actMalformed", "Account malformed.");
191 }
192 {
193 // Request an invalid ledger.
194 Json::Value const args{depositAuthArgs(alice, becky, "-1")};
195 Json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
196 verifyErr(
197 result, "invalidParams", "Invalid field 'ledger_index', not string or number.");
198 }
199 {
200 // Request a ledger that doesn't exist yet as a string.
201 Json::Value const args{depositAuthArgs(alice, becky, "17")};
202 Json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
203 verifyErr(result, "lgrNotFound", "ledgerNotFound");
204 }
205 {
206 // Request a ledger that doesn't exist yet.
207 Json::Value args{depositAuthArgs(alice, becky)};
208 args[jss::ledger_index] = 17;
209 Json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
210 verifyErr(result, "lgrNotFound", "ledgerNotFound");
211 }
212 {
213 // alice is not yet funded.
214 Json::Value const args{depositAuthArgs(alice, becky)};
215 Json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
216 verifyErr(result, "srcActNotFound", "Source account not found.");
217 }
218 env.fund(XRP(1000), alice);
219 env.close();
220 {
221 // becky is not yet funded.
222 Json::Value const args{depositAuthArgs(alice, becky)};
223 Json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
224 verifyErr(result, "dstActNotFound", "Destination account not found.");
225 }
226 env.fund(XRP(1000), becky);
227 env.close();
228 {
229 // Once becky is funded try it again and see it succeed.
230 Json::Value const args{depositAuthArgs(alice, becky)};
231 Json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
232 validateDepositAuthResult(result, true);
233 }
234 }
235
236 void
238 Json::Value const& result,
239 jtx::Account const& src,
240 jtx::Account const& dst,
241 bool authorized,
242 std::vector<std::string> credentialIDs = {},
243 std::string_view error = "")
244 {
245 BEAST_EXPECT(result[jss::status] == authorized ? jss::success : jss::error);
246 if (result.isMember(jss::deposit_authorized))
247 BEAST_EXPECT(result[jss::deposit_authorized] == authorized);
248 if (authorized)
249 {
250 BEAST_EXPECT(
251 result.isMember(jss::deposit_authorized) &&
252 (result[jss::deposit_authorized] == true));
253 }
254
255 BEAST_EXPECT(result.isMember(jss::error) == !error.empty());
256 if (!error.empty())
257 BEAST_EXPECT(result[jss::error].asString() == error);
258
259 if (authorized)
260 {
261 BEAST_EXPECT(result[jss::source_account] == src.human());
262 BEAST_EXPECT(result[jss::destination_account] == dst.human());
263
264 for (unsigned i = 0; i < credentialIDs.size(); ++i)
265 BEAST_EXPECT(result[jss::credentials][i] == credentialIDs[i]);
266 }
267 else
268 {
269 BEAST_EXPECT(result[jss::request].isObject());
270
271 auto const& request = result[jss::request];
272 BEAST_EXPECT(request[jss::command] == jss::deposit_authorized);
273 BEAST_EXPECT(request[jss::source_account] == src.human());
274 BEAST_EXPECT(request[jss::destination_account] == dst.human());
275
276 for (unsigned i = 0; i < credentialIDs.size(); ++i)
277 BEAST_EXPECT(request[jss::credentials][i] == credentialIDs[i]);
278 }
279 }
280
281 void
283 {
284 testcase("Credentials");
285
286 using namespace jtx;
287
288 char const credType[] = "abcde";
289
290 Account const alice{"alice"};
291 Account const becky{"becky"};
292 Account const diana{"diana"};
293 Account const carol{"carol"};
294
295 Env env(*this);
296 env.fund(XRP(1000), alice, becky, carol, diana);
297 env.close();
298
299 // carol recognize alice
300 env(credentials::create(alice, carol, credType));
301 env.close();
302 // retrieve the index of the credentials
303 auto const jv = credentials::ledgerEntry(env, alice, carol, credType);
304 std::string const credIdx = jv[jss::result][jss::index].asString();
305
306 // becky sets the DepositAuth flag in the current ledger.
307 env(fset(becky, asfDepositAuth));
308 env.close();
309
310 // becky authorize any account recognized by carol to make a payment
311 env(deposit::authCredentials(becky, {{carol, credType}}));
312 env.close();
313
314 {
315 testcase("deposit_authorized with credentials failure: empty array.");
316
317 auto args = depositAuthArgs(alice, becky, "validated");
318 args[jss::credentials] = Json::arrayValue;
319
320 auto const jv = env.rpc("json", "deposit_authorized", args.toStyledString());
321 checkCredentialsResponse(jv[jss::result], alice, becky, false, {}, "invalidParams");
322 }
323
324 {
325 testcase(
326 "deposit_authorized with credentials failure: not a string "
327 "credentials");
328
329 auto args = depositAuthArgs(alice, becky, "validated");
330 args[jss::credentials] = Json::arrayValue;
331 args[jss::credentials].append(1);
332 args[jss::credentials].append(3);
333
334 auto const jv = env.rpc("json", "deposit_authorized", args.toStyledString());
335 checkCredentialsResponse(jv[jss::result], alice, becky, false, {}, "invalidParams");
336 }
337
338 {
339 testcase(
340 "deposit_authorized with credentials failure: not a hex string "
341 "credentials");
342
343 auto args = depositAuthArgs(alice, becky, "validated");
344 args[jss::credentials] = Json::arrayValue;
345 args[jss::credentials].append("hello world");
346
347 auto const jv = env.rpc("json", "deposit_authorized", args.toStyledString());
349 jv[jss::result], alice, becky, false, {"hello world"}, "invalidParams");
350 }
351
352 {
353 testcase(
354 "deposit_authorized with credentials failure: not a credential "
355 "index");
356
357 auto args = depositAuthArgs(
358 alice,
359 becky,
360 "validated",
361 {"0127AB8B4B29CCDBB61AA51C0799A8A6BB80B86A9899807C11ED576AF8516"
362 "473"});
363
364 auto const jv = env.rpc("json", "deposit_authorized", args.toStyledString());
366 jv[jss::result],
367 alice,
368 becky,
369 false,
370 {"0127AB8B4B29CCDBB61AA51C0799A8A6BB80B86A9899807C11ED576AF8516"
371 "473"},
372 "badCredentials");
373 }
374
375 {
376 testcase(
377 "deposit_authorized with credentials not authorized: "
378 "credential not accepted");
379 auto const jv = env.rpc(
380 "json",
381 "deposit_authorized",
382 depositAuthArgs(alice, becky, "validated", {credIdx}).toStyledString());
384 jv[jss::result], alice, becky, false, {credIdx}, "badCredentials");
385 }
386
387 // alice accept credentials
388 env(credentials::accept(alice, carol, credType));
389 env.close();
390
391 {
392 testcase("deposit_authorized with duplicates in credentials");
393 auto const jv = env.rpc(
394 "json",
395 "deposit_authorized",
396 depositAuthArgs(alice, becky, "validated", {credIdx, credIdx}).toStyledString());
398 jv[jss::result], alice, becky, false, {credIdx, credIdx}, "badCredentials");
399 }
400
401 {
402 static std::vector<std::string> const credIds = {
403 "18004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288BE4",
404 "28004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288BE4",
405 "38004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288BE4",
406 "48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288BE4",
407 "58004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288BE4",
408 "68004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288BE4",
409 "78004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288BE4",
410 "88004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288BE4",
411 "98004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288BE4",
412 };
413 assert(credIds.size() > maxCredentialsArraySize);
414
415 testcase("deposit_authorized too long credentials");
416 auto const jv = env.rpc(
417 "json",
418 "deposit_authorized",
419 depositAuthArgs(alice, becky, "validated", credIds).toStyledString());
421 jv[jss::result], alice, becky, false, credIds, "invalidParams");
422 }
423
424 {
425 testcase("deposit_authorized with credentials");
426 auto const jv = env.rpc(
427 "json",
428 "deposit_authorized",
429 depositAuthArgs(alice, becky, "validated", {credIdx}).toStyledString());
430 checkCredentialsResponse(jv[jss::result], alice, becky, true, {credIdx});
431 }
432
433 {
434 // diana recognize becky
435 env(credentials::create(becky, diana, credType));
436 env.close();
437 env(credentials::accept(becky, diana, credType));
438 env.close();
439
440 // retrieve the index of the credentials
441 auto jv = credentials::ledgerEntry(env, becky, diana, credType);
442 std::string const credBecky = jv[jss::result][jss::index].asString();
443
444 testcase("deposit_authorized account without preauth");
445 jv = env.rpc(
446 "json",
447 "deposit_authorized",
448 depositAuthArgs(becky, alice, "validated", {credBecky}).toStyledString());
449 checkCredentialsResponse(jv[jss::result], becky, alice, true, {credBecky});
450 }
451
452 {
453 // carol recognize diana
454 env(credentials::create(diana, carol, credType));
455 env.close();
456 env(credentials::accept(diana, carol, credType));
457 env.close();
458 // retrieve the index of the credentials
459 auto jv = credentials::ledgerEntry(env, alice, carol, credType);
460 std::string const credDiana = jv[jss::result][jss::index].asString();
461
462 // alice try to use credential for different account
463 jv = env.rpc(
464 "json",
465 "deposit_authorized",
466 depositAuthArgs(becky, alice, "validated", {credDiana}).toStyledString());
468 jv[jss::result], becky, alice, false, {credDiana}, "badCredentials");
469 }
470
471 {
472 testcase("deposit_authorized with expired credentials");
473
474 // check expired credentials
475 char const credType2[] = "random";
476 std::uint32_t const x =
477 env.current()->header().parentCloseTime.time_since_epoch().count() + 40;
478
479 // create credentials with expire time 40s
480 auto jv = credentials::create(alice, carol, credType2);
481 jv[sfExpiration.jsonName] = x;
482 env(jv);
483 env.close();
484 env(credentials::accept(alice, carol, credType2));
485 env.close();
486 jv = credentials::ledgerEntry(env, alice, carol, credType2);
487 std::string const credIdx2 = jv[jss::result][jss::index].asString();
488
489 // becky sets the DepositAuth flag in the current ledger.
490 env(fset(becky, asfDepositAuth));
491 env.close();
492
493 // becky authorize any account recognized by carol to make a payment
494 env(deposit::authCredentials(becky, {{carol, credType2}}));
495 env.close();
496
497 {
498 // this should be fine
499 jv = env.rpc(
500 "json",
501 "deposit_authorized",
502 depositAuthArgs(alice, becky, "validated", {credIdx2}).toStyledString());
503 checkCredentialsResponse(jv[jss::result], alice, becky, true, {credIdx2});
504 }
505
506 // increase timer by 20s
507 env.close();
508 env.close();
509 {
510 // now credentials expired
511 jv = env.rpc(
512 "json",
513 "deposit_authorized",
514 depositAuthArgs(alice, becky, "validated", {credIdx2}).toStyledString());
515
517 jv[jss::result], alice, becky, false, {credIdx2}, "badCredentials");
518 }
519 }
520 }
521
522 void
523 run() override
524 {
525 testValid();
526 testErrors();
528 }
529};
530
531BEAST_DEFINE_TESTSUITE(DepositAuthorized, rpc, xrpl);
532
533} // namespace test
534} // namespace xrpl
Represents a JSON value.
Definition json_value.h:130
Value removeMember(char const *key)
Remove and return the named member.
bool isMember(char const *key) const
Return true if the object has a member named key.
A testsuite class.
Definition suite.h:51
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:150
void validateDepositAuthResult(Json::Value const &result, bool authorized)
void checkCredentialsResponse(Json::Value const &result, jtx::Account const &src, jtx::Account const &dst, bool authorized, std::vector< std::string > credentialIDs={}, std::string_view error="")
static Json::Value depositAuthArgs(jtx::Account const &source, jtx::Account const &dest, std::string const &ledger="", std::vector< std::string > const &credentials={})
Immutable cryptographic account descriptor.
Definition Account.h:19
std::string const & human() const
Returns the human readable public key.
Definition Account.h:94
A transaction testing environment.
Definition Env.h:122
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:100
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:270
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:847
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:329
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition rpc.h:15
@ arrayValue
array value (ordered list)
Definition json_value.h:25
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:26
Json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:26
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:53
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:13
Json::Value authCredentials(jtx::Account const &account, std::vector< AuthorizeCredentials > const &auth)
Definition deposit.cpp:35
Json::Value auth(Account const &account, Account const &auth)
Preauthorize for deposit.
Definition deposit.cpp:13
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:95
Json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
Definition flags.h:101
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:10
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
static bool authorized(Port const &port, std::map< std::string, std::string > const &h)
std::size_t constexpr maxCredentialsArraySize
The maximum number of credentials can be passed in array.
Definition Protocol.h:224
T size(T... args)