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