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("json", "deposit_authorized", depositAuthArgs(becky, becky, "validated").toStyledString()), true);
62
63 // alice should currently be authorized to deposit to becky.
65 env.rpc("json", "deposit_authorized", depositAuthArgs(alice, becky, "validated").toStyledString()), true);
66
67 // becky sets the DepositAuth flag in the current ledger.
68 env(fset(becky, asfDepositAuth));
69
70 // alice is no longer authorized to deposit to becky in current ledger.
72 env.rpc("json", "deposit_authorized", depositAuthArgs(alice, becky).toStyledString()), false);
73 env.close();
74
75 // becky is still authorized to deposit to herself.
77 env.rpc("json", "deposit_authorized", depositAuthArgs(becky, becky, "validated").toStyledString()), true);
78
79 // It's not a reciprocal arrangement. becky can deposit to alice.
81 env.rpc("json", "deposit_authorized", depositAuthArgs(becky, alice, "current").toStyledString()), true);
82
83 // becky creates a deposit authorization for alice.
84 env(deposit::auth(becky, alice));
85 env.close();
86
87 // alice is now authorized to deposit to becky.
89 env.rpc("json", "deposit_authorized", depositAuthArgs(alice, becky, "closed").toStyledString()), true);
90
91 // carol is still not authorized to deposit to becky.
93 env.rpc("json", "deposit_authorized", depositAuthArgs(carol, becky).toStyledString()), false);
94
95 // becky clears the DepositAuth flag so carol becomes authorized.
96 env(fclear(becky, asfDepositAuth));
97 env.close();
98
100 env.rpc("json", "deposit_authorized", depositAuthArgs(carol, becky).toStyledString()), true);
101
102 // alice is still authorized to deposit to becky.
104 env.rpc("json", "deposit_authorized", depositAuthArgs(alice, becky).toStyledString()), true);
105 }
106
107 // Test malformed cases.
108 void
110 {
111 testcase("Errors");
112 using namespace jtx;
113 Account const alice{"alice"};
114 Account const becky{"becky"};
115
116 // Lambda that checks the (error) result of deposit_authorized.
117 auto verifyErr = [this](Json::Value const& result, char const* error, char const* errorMsg) {
118 BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
119 BEAST_EXPECT(result[jss::result][jss::error] == error);
120 BEAST_EXPECT(result[jss::result][jss::error_message] == errorMsg);
121 };
122
123 Env env(*this);
124 {
125 // Missing source_account field.
126 Json::Value args{depositAuthArgs(alice, becky)};
127 args.removeMember(jss::source_account);
128 Json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
129 verifyErr(result, "invalidParams", "Missing field 'source_account'.");
130 }
131 {
132 // Non-string source_account field.
133 Json::Value args{depositAuthArgs(alice, becky)};
134 args[jss::source_account] = 7.3;
135 Json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
136 verifyErr(result, "invalidParams", "Invalid field 'source_account', not a string.");
137 }
138 {
139 // Corrupt source_account field.
140 Json::Value args{depositAuthArgs(alice, becky)};
141 args[jss::source_account] = "rG1QQv2nh2gr7RCZ!P8YYcBUKCCN633jCn";
142 Json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
143 verifyErr(result, "actMalformed", "Account malformed.");
144 }
145 {
146 // Missing destination_account field.
147 Json::Value args{depositAuthArgs(alice, becky)};
148 args.removeMember(jss::destination_account);
149 Json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
150 verifyErr(result, "invalidParams", "Missing field 'destination_account'.");
151 }
152 {
153 // Non-string destination_account field.
154 Json::Value args{depositAuthArgs(alice, becky)};
155 args[jss::destination_account] = 7.3;
156 Json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
157 verifyErr(result, "invalidParams", "Invalid field 'destination_account', not a string.");
158 }
159 {
160 // Corrupt destination_account field.
161 Json::Value args{depositAuthArgs(alice, becky)};
162 args[jss::destination_account] = "rP6P9ypfAmc!pw8SZHNwM4nvZHFXDraQas";
163 Json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
164 verifyErr(result, "actMalformed", "Account malformed.");
165 }
166 {
167 // Request an invalid ledger.
168 Json::Value args{depositAuthArgs(alice, becky, "-1")};
169 Json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
170 verifyErr(result, "invalidParams", "Invalid field 'ledger_index', not string or number.");
171 }
172 {
173 // Request a ledger that doesn't exist yet as a string.
174 Json::Value args{depositAuthArgs(alice, becky, "17")};
175 Json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
176 verifyErr(result, "lgrNotFound", "ledgerNotFound");
177 }
178 {
179 // Request a ledger that doesn't exist yet.
180 Json::Value args{depositAuthArgs(alice, becky)};
181 args[jss::ledger_index] = 17;
182 Json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
183 verifyErr(result, "lgrNotFound", "ledgerNotFound");
184 }
185 {
186 // alice is not yet funded.
187 Json::Value args{depositAuthArgs(alice, becky)};
188 Json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
189 verifyErr(result, "srcActNotFound", "Source account not found.");
190 }
191 env.fund(XRP(1000), alice);
192 env.close();
193 {
194 // becky is not yet funded.
195 Json::Value args{depositAuthArgs(alice, becky)};
196 Json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
197 verifyErr(result, "dstActNotFound", "Destination account not found.");
198 }
199 env.fund(XRP(1000), becky);
200 env.close();
201 {
202 // Once becky is funded try it again and see it succeed.
203 Json::Value args{depositAuthArgs(alice, becky)};
204 Json::Value const result{env.rpc("json", "deposit_authorized", args.toStyledString())};
205 validateDepositAuthResult(result, true);
206 }
207 }
208
209 void
211 Json::Value const& result,
212 jtx::Account const& src,
213 jtx::Account const& dst,
214 bool authorized,
215 std::vector<std::string> credentialIDs = {},
216 std::string_view error = "")
217 {
218 BEAST_EXPECT(result[jss::status] == authorized ? jss::success : jss::error);
219 if (result.isMember(jss::deposit_authorized))
220 BEAST_EXPECT(result[jss::deposit_authorized] == authorized);
221 if (authorized)
222 BEAST_EXPECT(result.isMember(jss::deposit_authorized) && (result[jss::deposit_authorized] == true));
223
224 BEAST_EXPECT(result.isMember(jss::error) == !error.empty());
225 if (!error.empty())
226 BEAST_EXPECT(result[jss::error].asString() == error);
227
228 if (authorized)
229 {
230 BEAST_EXPECT(result[jss::source_account] == src.human());
231 BEAST_EXPECT(result[jss::destination_account] == dst.human());
232
233 for (unsigned i = 0; i < credentialIDs.size(); ++i)
234 BEAST_EXPECT(result[jss::credentials][i] == credentialIDs[i]);
235 }
236 else
237 {
238 BEAST_EXPECT(result[jss::request].isObject());
239
240 auto const& request = result[jss::request];
241 BEAST_EXPECT(request[jss::command] == jss::deposit_authorized);
242 BEAST_EXPECT(request[jss::source_account] == src.human());
243 BEAST_EXPECT(request[jss::destination_account] == dst.human());
244
245 for (unsigned i = 0; i < credentialIDs.size(); ++i)
246 BEAST_EXPECT(request[jss::credentials][i] == credentialIDs[i]);
247 }
248 }
249
250 void
252 {
253 testcase("Credentials");
254
255 using namespace jtx;
256
257 char const credType[] = "abcde";
258
259 Account const alice{"alice"};
260 Account const becky{"becky"};
261 Account const diana{"diana"};
262 Account const carol{"carol"};
263
264 Env env(*this);
265 env.fund(XRP(1000), alice, becky, carol, diana);
266 env.close();
267
268 // carol recognize alice
269 env(credentials::create(alice, carol, credType));
270 env.close();
271 // retrieve the index of the credentials
272 auto const jv = credentials::ledgerEntry(env, alice, carol, credType);
273 std::string const credIdx = jv[jss::result][jss::index].asString();
274
275 // becky sets the DepositAuth flag in the current ledger.
276 env(fset(becky, asfDepositAuth));
277 env.close();
278
279 // becky authorize any account recognized by carol to make a payment
280 env(deposit::authCredentials(becky, {{carol, credType}}));
281 env.close();
282
283 {
284 testcase("deposit_authorized with credentials failure: empty array.");
285
286 auto args = depositAuthArgs(alice, becky, "validated");
287 args[jss::credentials] = Json::arrayValue;
288
289 auto const jv = env.rpc("json", "deposit_authorized", args.toStyledString());
290 checkCredentialsResponse(jv[jss::result], alice, becky, false, {}, "invalidParams");
291 }
292
293 {
294 testcase(
295 "deposit_authorized with credentials failure: not a string "
296 "credentials");
297
298 auto args = depositAuthArgs(alice, becky, "validated");
299 args[jss::credentials] = Json::arrayValue;
300 args[jss::credentials].append(1);
301 args[jss::credentials].append(3);
302
303 auto const jv = env.rpc("json", "deposit_authorized", args.toStyledString());
304 checkCredentialsResponse(jv[jss::result], alice, becky, false, {}, "invalidParams");
305 }
306
307 {
308 testcase(
309 "deposit_authorized with credentials failure: not a hex string "
310 "credentials");
311
312 auto args = depositAuthArgs(alice, becky, "validated");
313 args[jss::credentials] = Json::arrayValue;
314 args[jss::credentials].append("hello world");
315
316 auto const jv = env.rpc("json", "deposit_authorized", args.toStyledString());
317 checkCredentialsResponse(jv[jss::result], alice, becky, false, {"hello world"}, "invalidParams");
318 }
319
320 {
321 testcase(
322 "deposit_authorized with credentials failure: not a credential "
323 "index");
324
325 auto args = depositAuthArgs(
326 alice,
327 becky,
328 "validated",
329 {"0127AB8B4B29CCDBB61AA51C0799A8A6BB80B86A9899807C11ED576AF8516"
330 "473"});
331
332 auto const jv = env.rpc("json", "deposit_authorized", args.toStyledString());
334 jv[jss::result],
335 alice,
336 becky,
337 false,
338 {"0127AB8B4B29CCDBB61AA51C0799A8A6BB80B86A9899807C11ED576AF8516"
339 "473"},
340 "badCredentials");
341 }
342
343 {
344 testcase(
345 "deposit_authorized with credentials not authorized: "
346 "credential not accepted");
347 auto const jv = env.rpc(
348 "json", "deposit_authorized", depositAuthArgs(alice, becky, "validated", {credIdx}).toStyledString());
349 checkCredentialsResponse(jv[jss::result], alice, becky, false, {credIdx}, "badCredentials");
350 }
351
352 // alice accept credentials
353 env(credentials::accept(alice, carol, credType));
354 env.close();
355
356 {
357 testcase("deposit_authorized with duplicates in credentials");
358 auto const jv = env.rpc(
359 "json",
360 "deposit_authorized",
361 depositAuthArgs(alice, becky, "validated", {credIdx, credIdx}).toStyledString());
362 checkCredentialsResponse(jv[jss::result], alice, becky, false, {credIdx, credIdx}, "badCredentials");
363 }
364
365 {
366 static std::vector<std::string> const credIds = {
367 "18004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
368 "E4",
369 "28004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
370 "E4",
371 "38004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
372 "E4",
373 "48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
374 "E4",
375 "58004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
376 "E4",
377 "68004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
378 "E4",
379 "78004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
380 "E4",
381 "88004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
382 "E4",
383 "98004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B"
384 "E4"};
385 assert(credIds.size() > maxCredentialsArraySize);
386
387 testcase("deposit_authorized too long credentials");
388 auto const jv = env.rpc(
389 "json", "deposit_authorized", depositAuthArgs(alice, becky, "validated", credIds).toStyledString());
390 checkCredentialsResponse(jv[jss::result], alice, becky, false, credIds, "invalidParams");
391 }
392
393 {
394 testcase("deposit_authorized with credentials");
395 auto const jv = env.rpc(
396 "json", "deposit_authorized", depositAuthArgs(alice, becky, "validated", {credIdx}).toStyledString());
397 checkCredentialsResponse(jv[jss::result], alice, becky, true, {credIdx});
398 }
399
400 {
401 // diana recognize becky
402 env(credentials::create(becky, diana, credType));
403 env.close();
404 env(credentials::accept(becky, diana, credType));
405 env.close();
406
407 // retrieve the index of the credentials
408 auto jv = credentials::ledgerEntry(env, becky, diana, credType);
409 std::string const credBecky = jv[jss::result][jss::index].asString();
410
411 testcase("deposit_authorized account without preauth");
412 jv = env.rpc(
413 "json", "deposit_authorized", depositAuthArgs(becky, alice, "validated", {credBecky}).toStyledString());
414 checkCredentialsResponse(jv[jss::result], becky, alice, true, {credBecky});
415 }
416
417 {
418 // carol recognize diana
419 env(credentials::create(diana, carol, credType));
420 env.close();
421 env(credentials::accept(diana, carol, credType));
422 env.close();
423 // retrieve the index of the credentials
424 auto jv = credentials::ledgerEntry(env, alice, carol, credType);
425 std::string const credDiana = jv[jss::result][jss::index].asString();
426
427 // alice try to use credential for different account
428 jv = env.rpc(
429 "json", "deposit_authorized", depositAuthArgs(becky, alice, "validated", {credDiana}).toStyledString());
430 checkCredentialsResponse(jv[jss::result], becky, alice, false, {credDiana}, "badCredentials");
431 }
432
433 {
434 testcase("deposit_authorized with expired credentials");
435
436 // check expired credentials
437 char const credType2[] = "random";
438 std::uint32_t const x = env.current()->header().parentCloseTime.time_since_epoch().count() + 40;
439
440 // create credentials with expire time 40s
441 auto jv = credentials::create(alice, carol, credType2);
442 jv[sfExpiration.jsonName] = x;
443 env(jv);
444 env.close();
445 env(credentials::accept(alice, carol, credType2));
446 env.close();
447 jv = credentials::ledgerEntry(env, alice, carol, credType2);
448 std::string const credIdx2 = jv[jss::result][jss::index].asString();
449
450 // becky sets the DepositAuth flag in the current ledger.
451 env(fset(becky, asfDepositAuth));
452 env.close();
453
454 // becky authorize any account recognized by carol to make a payment
455 env(deposit::authCredentials(becky, {{carol, credType2}}));
456 env.close();
457
458 {
459 // this should be fine
460 jv = env.rpc(
461 "json",
462 "deposit_authorized",
463 depositAuthArgs(alice, becky, "validated", {credIdx2}).toStyledString());
464 checkCredentialsResponse(jv[jss::result], alice, becky, true, {credIdx2});
465 }
466
467 // increase timer by 20s
468 env.close();
469 env.close();
470 {
471 // now credentials expired
472 jv = env.rpc(
473 "json",
474 "deposit_authorized",
475 depositAuthArgs(alice, becky, "validated", {credIdx2}).toStyledString());
476
477 checkCredentialsResponse(jv[jss::result], alice, becky, false, {credIdx2}, "badCredentials");
478 }
479 }
480 }
481
482 void
483 run() override
484 {
485 testValid();
486 testErrors();
488 }
489};
490
491BEAST_DEFINE_TESTSUITE(DepositAuthorized, rpc, xrpl);
492
493} // namespace test
494} // 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:147
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:119
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:98
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:261
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:792
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:319
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:49
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:90
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)
constexpr std::uint32_t asfDepositAuth
Definition TxFlags.h:65
std::size_t constexpr maxCredentialsArraySize
The maximum number of credentials can be passed in array.
Definition Protocol.h:223
T size(T... args)