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