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