rippled
Loading...
Searching...
No Matches
DepositAuth_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2017 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/Feature.h>
23
24#include <algorithm>
25
26namespace ripple {
27namespace test {
28
29// Helper function that returns the reserve on an account based on
30// the passed in number of owners.
31static XRPAmount
33{
34 return env.current()->fees().accountReserve(count);
35}
36
37// Helper function that returns true if acct has the lsfDepostAuth flag set.
38static bool
39hasDepositAuth(jtx::Env const& env, jtx::Account const& acct)
40{
41 return ((*env.le(acct))[sfFlags] & lsfDepositAuth) == lsfDepositAuth;
42}
43
45{
46 void
48 {
49 testcase("Enable");
50
51 using namespace jtx;
52 Account const alice{"alice"};
53
54 {
55 // featureDepositAuth is disabled.
56 Env env(*this, supported_amendments() - featureDepositAuth);
57 env.fund(XRP(10000), alice);
58
59 // Note that, to support old behavior, invalid flags are ignored.
60 env(fset(alice, asfDepositAuth));
61 env.close();
62 BEAST_EXPECT(!hasDepositAuth(env, alice));
63
64 env(fclear(alice, asfDepositAuth));
65 env.close();
66 BEAST_EXPECT(!hasDepositAuth(env, alice));
67 }
68 {
69 // featureDepositAuth is enabled.
70 Env env(*this);
71 env.fund(XRP(10000), alice);
72
73 env(fset(alice, asfDepositAuth));
74 env.close();
75 BEAST_EXPECT(hasDepositAuth(env, alice));
76
77 env(fclear(alice, asfDepositAuth));
78 env.close();
79 BEAST_EXPECT(!hasDepositAuth(env, alice));
80 }
81 }
82
83 void
85 {
86 // Exercise IOU payments and non-direct XRP payments to an account
87 // that has the lsfDepositAuth flag set.
88 testcase("Pay IOU");
89
90 using namespace jtx;
91 Account const alice{"alice"};
92 Account const bob{"bob"};
93 Account const carol{"carol"};
94 Account const gw{"gw"};
95 IOU const USD = gw["USD"];
96
97 Env env(*this);
98
99 env.fund(XRP(10000), alice, bob, carol, gw);
100 env.trust(USD(1000), alice, bob);
101 env.close();
102
103 env(pay(gw, alice, USD(150)));
104 env(offer(carol, USD(100), XRP(100)));
105 env.close();
106
107 // Make sure bob's trust line is all set up so he can receive USD.
108 env(pay(alice, bob, USD(50)));
109 env.close();
110
111 // bob sets the lsfDepositAuth flag.
113 env.close();
114
115 // None of the following payments should succeed.
116 auto failedIouPayments = [this, &env, &alice, &bob, &USD]() {
117 env.require(flags(bob, asfDepositAuth));
118
119 // Capture bob's balances before hand to confirm they don't change.
120 PrettyAmount const bobXrpBalance{env.balance(bob, XRP)};
121 PrettyAmount const bobUsdBalance{env.balance(bob, USD)};
122
123 env(pay(alice, bob, USD(50)), ter(tecNO_PERMISSION));
124 env.close();
125
126 // Note that even though alice is paying bob in XRP, the payment
127 // is still not allowed since the payment passes through an offer.
128 env(pay(alice, bob, drops(1)),
129 sendmax(USD(1)),
131 env.close();
132
133 BEAST_EXPECT(bobXrpBalance == env.balance(bob, XRP));
134 BEAST_EXPECT(bobUsdBalance == env.balance(bob, USD));
135 };
136
137 // Test when bob has an XRP balance > base reserve.
138 failedIouPayments();
139
140 // Set bob's XRP balance == base reserve. Also demonstrate that
141 // bob can make payments while his lsfDepositAuth flag is set.
142 env(pay(bob, alice, USD(25)));
143 env.close();
144
145 {
146 STAmount const bobPaysXRP{env.balance(bob, XRP) - reserve(env, 1)};
147 XRPAmount const bobPaysFee{reserve(env, 1) - reserve(env, 0)};
148 env(pay(bob, alice, bobPaysXRP), fee(bobPaysFee));
149 env.close();
150 }
151
152 // Test when bob's XRP balance == base reserve.
153 BEAST_EXPECT(env.balance(bob, XRP) == reserve(env, 0));
154 BEAST_EXPECT(env.balance(bob, USD) == USD(25));
155 failedIouPayments();
156
157 // Test when bob has an XRP balance == 0.
158 env(noop(bob), fee(reserve(env, 0)));
159 env.close();
160
161 BEAST_EXPECT(env.balance(bob, XRP) == XRP(0));
162 failedIouPayments();
163
164 // Give bob enough XRP for the fee to clear the lsfDepositAuth flag.
165 env(pay(alice, bob, drops(env.current()->fees().base)));
166
167 // bob clears the lsfDepositAuth and the next payment succeeds.
168 env(fclear(bob, asfDepositAuth));
169 env.close();
170
171 env(pay(alice, bob, USD(50)));
172 env.close();
173
174 env(pay(alice, bob, drops(1)), sendmax(USD(1)));
175 env.close();
176 }
177
178 void
180 {
181 // Exercise direct XRP payments to an account that has the
182 // lsfDepositAuth flag set.
183 testcase("Pay XRP");
184
185 using namespace jtx;
186 Account const alice{"alice"};
187 Account const bob{"bob"};
188
189 Env env(*this);
190
191 env.fund(XRP(10000), alice, bob);
192
193 // bob sets the lsfDepositAuth flag.
194 env(fset(bob, asfDepositAuth), fee(drops(10)));
195 env.close();
196 BEAST_EXPECT(env.balance(bob, XRP) == XRP(10000) - drops(10));
197
198 // bob has more XRP than the base reserve. Any XRP payment should fail.
199 env(pay(alice, bob, drops(1)), ter(tecNO_PERMISSION));
200 env.close();
201 BEAST_EXPECT(env.balance(bob, XRP) == XRP(10000) - drops(10));
202
203 // Change bob's XRP balance to exactly the base reserve.
204 {
205 STAmount const bobPaysXRP{env.balance(bob, XRP) - reserve(env, 1)};
206 XRPAmount const bobPaysFee{reserve(env, 1) - reserve(env, 0)};
207 env(pay(bob, alice, bobPaysXRP), fee(bobPaysFee));
208 env.close();
209 }
210
211 // bob has exactly the base reserve. A small enough direct XRP
212 // payment should succeed.
213 BEAST_EXPECT(env.balance(bob, XRP) == reserve(env, 0));
214 env(pay(alice, bob, drops(1)));
215 env.close();
216
217 // bob has exactly the base reserve + 1. No payment should succeed.
218 BEAST_EXPECT(env.balance(bob, XRP) == reserve(env, 0) + drops(1));
219 env(pay(alice, bob, drops(1)), ter(tecNO_PERMISSION));
220 env.close();
221
222 // Take bob down to a balance of 0 XRP.
223 env(noop(bob), fee(reserve(env, 0) + drops(1)));
224 env.close();
225 BEAST_EXPECT(env.balance(bob, XRP) == drops(0));
226
227 // We should not be able to pay bob more than the base reserve.
228 env(pay(alice, bob, reserve(env, 0) + drops(1)), ter(tecNO_PERMISSION));
229 env.close();
230
231 // However a payment of exactly the base reserve should succeed.
232 env(pay(alice, bob, reserve(env, 0) + drops(0)));
233 env.close();
234 BEAST_EXPECT(env.balance(bob, XRP) == reserve(env, 0));
235
236 // We should be able to pay bob the base reserve one more time.
237 env(pay(alice, bob, reserve(env, 0) + drops(0)));
238 env.close();
239 BEAST_EXPECT(
240 env.balance(bob, XRP) == (reserve(env, 0) + reserve(env, 0)));
241
242 // bob's above the threshold again. Any payment should fail.
243 env(pay(alice, bob, drops(1)), ter(tecNO_PERMISSION));
244 env.close();
245 BEAST_EXPECT(
246 env.balance(bob, XRP) == (reserve(env, 0) + reserve(env, 0)));
247
248 // Take bob back down to a zero XRP balance.
249 env(noop(bob), fee(env.balance(bob, XRP)));
250 env.close();
251 BEAST_EXPECT(env.balance(bob, XRP) == drops(0));
252
253 // bob should not be able to clear lsfDepositAuth.
255 env.close();
256
257 // We should be able to pay bob 1 drop now.
258 env(pay(alice, bob, drops(1)));
259 env.close();
260 BEAST_EXPECT(env.balance(bob, XRP) == drops(1));
261
262 // Pay bob enough so he can afford the fee to clear lsfDepositAuth.
263 env(pay(alice, bob, drops(9)));
264 env.close();
265
266 // Interestingly, at this point the terINSUF_FEE_B retry grabs the
267 // request to clear lsfDepositAuth. So the balance should be zero
268 // and lsfDepositAuth should be cleared.
269 BEAST_EXPECT(env.balance(bob, XRP) == drops(0));
270 env.require(nflags(bob, asfDepositAuth));
271
272 // Since bob no longer has lsfDepositAuth set we should be able to
273 // pay him more than the base reserve.
274 env(pay(alice, bob, reserve(env, 0) + drops(1)));
275 env.close();
276 BEAST_EXPECT(env.balance(bob, XRP) == reserve(env, 0) + drops(1));
277 }
278
279 void
281 {
282 // It its current incarnation the DepositAuth flag does not change
283 // any behaviors regarding rippling and the NoRipple flag.
284 // Demonstrate that.
285 testcase("No Ripple");
286
287 using namespace jtx;
288 Account const gw1("gw1");
289 Account const gw2("gw2");
290 Account const alice("alice");
291 Account const bob("bob");
292
293 IOU const USD1(gw1["USD"]);
294 IOU const USD2(gw2["USD"]);
295
296 auto testIssuer = [&](FeatureBitset const& features,
297 bool noRipplePrev,
298 bool noRippleNext,
299 bool withDepositAuth) {
300 assert(!withDepositAuth || features[featureDepositAuth]);
301
302 Env env(*this, features);
303
304 env.fund(XRP(10000), gw1, alice, bob);
305 env(trust(gw1, alice["USD"](10), noRipplePrev ? tfSetNoRipple : 0));
306 env(trust(gw1, bob["USD"](10), noRippleNext ? tfSetNoRipple : 0));
307 env.trust(USD1(10), alice, bob);
308
309 env(pay(gw1, alice, USD1(10)));
310
311 if (withDepositAuth)
312 env(fset(gw1, asfDepositAuth));
313
314 TER const result = (noRippleNext && noRipplePrev) ? TER{tecPATH_DRY}
315 : TER{tesSUCCESS};
316 env(pay(alice, bob, USD1(10)), path(gw1), ter(result));
317 };
318
319 auto testNonIssuer = [&](FeatureBitset const& features,
320 bool noRipplePrev,
321 bool noRippleNext,
322 bool withDepositAuth) {
323 assert(!withDepositAuth || features[featureDepositAuth]);
324
325 Env env(*this, features);
326
327 env.fund(XRP(10000), gw1, gw2, alice);
328 env(trust(alice, USD1(10), noRipplePrev ? tfSetNoRipple : 0));
329 env(trust(alice, USD2(10), noRippleNext ? tfSetNoRipple : 0));
330 env(pay(gw2, alice, USD2(10)));
331
332 if (withDepositAuth)
333 env(fset(alice, asfDepositAuth));
334
335 TER const result = (noRippleNext && noRipplePrev) ? TER{tecPATH_DRY}
336 : TER{tesSUCCESS};
337 env(pay(gw1, gw2, USD2(10)),
338 path(alice),
339 sendmax(USD1(10)),
340 ter(result));
341 };
342
343 // Test every combo of noRipplePrev, noRippleNext, and withDepositAuth
344 for (int i = 0; i < 8; ++i)
345 {
346 auto const noRipplePrev = i & 0x1;
347 auto const noRippleNext = i & 0x2;
348 auto const withDepositAuth = i & 0x4;
349 testIssuer(
350 supported_amendments() | featureDepositAuth,
351 noRipplePrev,
352 noRippleNext,
353 withDepositAuth);
354
355 if (!withDepositAuth)
356 testIssuer(
357 supported_amendments() - featureDepositAuth,
358 noRipplePrev,
359 noRippleNext,
360 withDepositAuth);
361
362 testNonIssuer(
363 supported_amendments() | featureDepositAuth,
364 noRipplePrev,
365 noRippleNext,
366 withDepositAuth);
367
368 if (!withDepositAuth)
369 testNonIssuer(
370 supported_amendments() - featureDepositAuth,
371 noRipplePrev,
372 noRippleNext,
373 withDepositAuth);
374 }
375 }
376
377 void
378 run() override
379 {
380 testEnable();
381 testPayIOU();
382 testPayXRP();
383 testNoRipple();
384 }
385};
386
387static Json::Value
389 jtx::Env& env,
390 jtx::Account const& acc,
392{
393 Json::Value jvParams;
394 jvParams[jss::ledger_index] = jss::validated;
395 jvParams[jss::deposit_preauth][jss::owner] = acc.human();
396 jvParams[jss::deposit_preauth][jss::authorized_credentials] =
398 auto& arr(jvParams[jss::deposit_preauth][jss::authorized_credentials]);
399 for (auto const& o : auth)
400 {
401 arr.append(o.toLEJson());
402 }
403 return env.rpc("json", "ledger_entry", to_string(jvParams));
404}
405
407{
408 void
410 {
411 testcase("Enable");
412
413 using namespace jtx;
414 Account const alice{"alice"};
415 Account const becky{"becky"};
416 {
417 // featureDepositPreauth is disabled.
418 Env env(*this, supported_amendments() - featureDepositPreauth);
419 env.fund(XRP(10000), alice, becky);
420 env.close();
421
422 // Should not be able to add a DepositPreauth to alice.
423 env(deposit::auth(alice, becky), ter(temDISABLED));
424 env.close();
425 env.require(owners(alice, 0));
426 env.require(owners(becky, 0));
427
428 // Should not be able to remove a DepositPreauth from alice.
429 env(deposit::unauth(alice, becky), ter(temDISABLED));
430 env.close();
431 env.require(owners(alice, 0));
432 env.require(owners(becky, 0));
433 }
434 {
435 // featureDepositPreauth is enabled. The valid case is really
436 // simple:
437 // o We should be able to add and remove an entry, and
438 // o That entry should cost one reserve.
439 // o The reserve should be returned when the entry is removed.
440 Env env(*this);
441 env.fund(XRP(10000), alice, becky);
442 env.close();
443
444 // Add a DepositPreauth to alice.
445 env(deposit::auth(alice, becky));
446 env.close();
447 env.require(owners(alice, 1));
448 env.require(owners(becky, 0));
449
450 // Remove a DepositPreauth from alice.
451 env(deposit::unauth(alice, becky));
452 env.close();
453 env.require(owners(alice, 0));
454 env.require(owners(becky, 0));
455 }
456 {
457 // Verify that an account can be preauthorized and unauthorized
458 // using tickets.
459 Env env(*this);
460 env.fund(XRP(10000), alice, becky);
461 env.close();
462
463 env(ticket::create(alice, 2));
464 std::uint32_t const aliceSeq{env.seq(alice)};
465 env.close();
466 env.require(tickets(alice, 2));
467
468 // Consume the tickets from biggest seq to smallest 'cuz we can.
469 std::uint32_t aliceTicketSeq{env.seq(alice)};
470
471 // Add a DepositPreauth to alice.
472 env(deposit::auth(alice, becky), ticket::use(--aliceTicketSeq));
473 env.close();
474 // Alice uses a ticket but gains a preauth entry.
475 env.require(tickets(alice, 1));
476 env.require(owners(alice, 2));
477 BEAST_EXPECT(env.seq(alice) == aliceSeq);
478 env.require(owners(becky, 0));
479
480 // Remove a DepositPreauth from alice.
481 env(deposit::unauth(alice, becky), ticket::use(--aliceTicketSeq));
482 env.close();
483 env.require(tickets(alice, 0));
484 env.require(owners(alice, 0));
485 BEAST_EXPECT(env.seq(alice) == aliceSeq);
486 env.require(owners(becky, 0));
487 }
488 }
489
490 void
492 {
493 testcase("Invalid");
494
495 using namespace jtx;
496 Account const alice{"alice"};
497 Account const becky{"becky"};
498 Account const carol{"carol"};
499
500 Env env(*this);
501
502 // Tell env about alice, becky and carol since they are not yet funded.
503 env.memoize(alice);
504 env.memoize(becky);
505 env.memoize(carol);
506
507 // Add DepositPreauth to an unfunded account.
508 env(deposit::auth(alice, becky), seq(1), ter(terNO_ACCOUNT));
509
510 env.fund(XRP(10000), alice, becky);
511 env.close();
512
513 // Bad fee.
514 env(deposit::auth(alice, becky), fee(drops(-10)), ter(temBAD_FEE));
515 env.close();
516
517 // Bad flags.
518 env(deposit::auth(alice, becky), txflags(tfSell), ter(temINVALID_FLAG));
519 env.close();
520
521 {
522 // Neither auth not unauth.
523 Json::Value tx{deposit::auth(alice, becky)};
524 tx.removeMember(sfAuthorize.jsonName);
525 env(tx, ter(temMALFORMED));
526 env.close();
527 }
528 {
529 // Both auth and unauth.
530 Json::Value tx{deposit::auth(alice, becky)};
531 tx[sfUnauthorize.jsonName] = becky.human();
532 env(tx, ter(temMALFORMED));
533 env.close();
534 }
535 {
536 // Alice authorizes a zero account.
537 Json::Value tx{deposit::auth(alice, becky)};
538 tx[sfAuthorize.jsonName] = to_string(xrpAccount());
539 env(tx, ter(temINVALID_ACCOUNT_ID));
540 env.close();
541 }
542
543 // alice authorizes herself.
544 env(deposit::auth(alice, alice), ter(temCANNOT_PREAUTH_SELF));
545 env.close();
546
547 // alice authorizes an unfunded account.
548 env(deposit::auth(alice, carol), ter(tecNO_TARGET));
549 env.close();
550
551 // alice successfully authorizes becky.
552 env.require(owners(alice, 0));
553 env.require(owners(becky, 0));
554 env(deposit::auth(alice, becky));
555 env.close();
556 env.require(owners(alice, 1));
557 env.require(owners(becky, 0));
558
559 // alice attempts to create a duplicate authorization.
560 env(deposit::auth(alice, becky), ter(tecDUPLICATE));
561 env.close();
562 env.require(owners(alice, 1));
563 env.require(owners(becky, 0));
564
565 // carol attempts to preauthorize but doesn't have enough reserve.
566 env.fund(drops(249'999'999), carol);
567 env.close();
568
569 env(deposit::auth(carol, becky), ter(tecINSUFFICIENT_RESERVE));
570 env.close();
571 env.require(owners(carol, 0));
572 env.require(owners(becky, 0));
573
574 // carol gets enough XRP to (barely) meet the reserve.
575 env(pay(alice, carol, drops(11)));
576 env.close();
577 env(deposit::auth(carol, becky));
578 env.close();
579 env.require(owners(carol, 1));
580 env.require(owners(becky, 0));
581
582 // But carol can't meet the reserve for another preauthorization.
583 env(deposit::auth(carol, alice), ter(tecINSUFFICIENT_RESERVE));
584 env.close();
585 env.require(owners(carol, 1));
586 env.require(owners(becky, 0));
587 env.require(owners(alice, 1));
588
589 // alice attempts to remove an authorization she doesn't have.
590 env(deposit::unauth(alice, carol), ter(tecNO_ENTRY));
591 env.close();
592 env.require(owners(alice, 1));
593 env.require(owners(becky, 0));
594
595 // alice successfully removes her authorization of becky.
596 env(deposit::unauth(alice, becky));
597 env.close();
598 env.require(owners(alice, 0));
599 env.require(owners(becky, 0));
600
601 // alice removes becky again and gets an error.
602 env(deposit::unauth(alice, becky), ter(tecNO_ENTRY));
603 env.close();
604 env.require(owners(alice, 0));
605 env.require(owners(becky, 0));
606 }
607
608 void
610 {
611 testcase("Payment");
612
613 using namespace jtx;
614 Account const alice{"alice"};
615 Account const becky{"becky"};
616 Account const gw{"gw"};
617 IOU const USD(gw["USD"]);
618
619 bool const supportsPreauth = {features[featureDepositPreauth]};
620
621 {
622 // The initial implementation of DepositAuth had a bug where an
623 // account with the DepositAuth flag set could not make a payment
624 // to itself. That bug was fixed in the DepositPreauth amendment.
625 Env env(*this, features);
626 env.fund(XRP(5000), alice, becky, gw);
627 env.close();
628
629 env.trust(USD(1000), alice);
630 env.trust(USD(1000), becky);
631 env.close();
632
633 env(pay(gw, alice, USD(500)));
634 env.close();
635
636 env(offer(alice, XRP(100), USD(100), tfPassive),
637 require(offers(alice, 1)));
638 env.close();
639
640 // becky pays herself USD (10) by consuming part of alice's offer.
641 // Make sure the payment works if PaymentAuth is not involved.
642 env(pay(becky, becky, USD(10)), path(~USD), sendmax(XRP(10)));
643 env.close();
644
645 // becky decides to require authorization for deposits.
646 env(fset(becky, asfDepositAuth));
647 env.close();
648
649 // becky pays herself again. Whether it succeeds depends on
650 // whether featureDepositPreauth is enabled.
651 TER const expect{
652 supportsPreauth ? TER{tesSUCCESS} : TER{tecNO_PERMISSION}};
653
654 env(pay(becky, becky, USD(10)),
655 path(~USD),
656 sendmax(XRP(10)),
657 ter(expect));
658 env.close();
659
660 {
661 // becky setup depositpreauth with credentials
662 const char credType[] = "abcde";
663 Account const carol{"carol"};
664 env.fund(XRP(5000), carol);
665
666 bool const supportsCredentials = features[featureCredentials];
667
668 TER const expectCredentials(
669 supportsCredentials ? TER(tesSUCCESS) : TER(temDISABLED));
670 TER const expectPayment(
671 !supportsCredentials
673 : (!supportsPreauth ? TER(tecNO_PERMISSION)
674 : TER(tesSUCCESS)));
675 TER const expectDP(
676 !supportsPreauth
678 : (!supportsCredentials ? TER(temDISABLED)
679 : TER(tesSUCCESS)));
680
681 env(deposit::authCredentials(becky, {{carol, credType}}),
682 ter(expectDP));
683 env.close();
684
685 // gw accept credentials
686 env(credentials::create(gw, carol, credType),
687 ter(expectCredentials));
688 env.close();
689 env(credentials::accept(gw, carol, credType),
690 ter(expectCredentials));
691 env.close();
692
693 auto jv = credentials::ledgerEntry(env, gw, carol, credType);
694 std::string const credIdx = supportsCredentials
695 ? jv[jss::result][jss::index].asString()
696 : "48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6"
697 "EA288BE4";
698
699 env(pay(gw, becky, USD(100)),
700 credentials::ids({credIdx}),
701 ter(expectPayment));
702 env.close();
703 }
704
705 {
706 using namespace std::chrono;
707
708 if (!supportsPreauth)
709 {
710 auto const seq1 = env.seq(alice);
711 env(escrow(alice, becky, XRP(100)),
712 finish_time(env.now() + 1s));
713 env.close();
714
715 // Failed as rule is disabled
716 env(finish(gw, alice, seq1),
717 fee(1500),
719 env.close();
720 }
721 }
722 }
723
724 if (supportsPreauth)
725 {
726 // Make sure DepositPreauthorization works for payments.
727
728 Account const carol{"carol"};
729
730 Env env(*this, features);
731 env.fund(XRP(5000), alice, becky, carol, gw);
732 env.close();
733
734 env.trust(USD(1000), alice);
735 env.trust(USD(1000), becky);
736 env.trust(USD(1000), carol);
737 env.close();
738
739 env(pay(gw, alice, USD(1000)));
740 env.close();
741
742 // Make XRP and IOU payments from alice to becky. Should be fine.
743 env(pay(alice, becky, XRP(100)));
744 env(pay(alice, becky, USD(100)));
745 env.close();
746
747 // becky decides to require authorization for deposits.
748 env(fset(becky, asfDepositAuth));
749 env.close();
750
751 // alice can no longer pay becky.
752 env(pay(alice, becky, XRP(100)), ter(tecNO_PERMISSION));
753 env(pay(alice, becky, USD(100)), ter(tecNO_PERMISSION));
754 env.close();
755
756 // becky preauthorizes carol for deposit, which doesn't provide
757 // authorization for alice.
758 env(deposit::auth(becky, carol));
759 env.close();
760
761 // alice still can't pay becky.
762 env(pay(alice, becky, XRP(100)), ter(tecNO_PERMISSION));
763 env(pay(alice, becky, USD(100)), ter(tecNO_PERMISSION));
764 env.close();
765
766 // becky preauthorizes alice for deposit.
767 env(deposit::auth(becky, alice));
768 env.close();
769
770 // alice can now pay becky.
771 env(pay(alice, becky, XRP(100)));
772 env(pay(alice, becky, USD(100)));
773 env.close();
774
775 // alice decides to require authorization for deposits.
776 env(fset(alice, asfDepositAuth));
777 env.close();
778
779 // Even though alice is authorized to pay becky, becky is not
780 // authorized to pay alice.
781 env(pay(becky, alice, XRP(100)), ter(tecNO_PERMISSION));
782 env(pay(becky, alice, USD(100)), ter(tecNO_PERMISSION));
783 env.close();
784
785 // becky unauthorizes carol. Should have no impact on alice.
786 env(deposit::unauth(becky, carol));
787 env.close();
788
789 env(pay(alice, becky, XRP(100)));
790 env(pay(alice, becky, USD(100)));
791 env.close();
792
793 // becky unauthorizes alice. alice now can't pay becky.
794 env(deposit::unauth(becky, alice));
795 env.close();
796
797 env(pay(alice, becky, XRP(100)), ter(tecNO_PERMISSION));
798 env(pay(alice, becky, USD(100)), ter(tecNO_PERMISSION));
799 env.close();
800
801 // becky decides to remove authorization for deposits. Now
802 // alice can pay becky again.
803 env(fclear(becky, asfDepositAuth));
804 env.close();
805
806 env(pay(alice, becky, XRP(100)));
807 env(pay(alice, becky, USD(100)));
808 env.close();
809 }
810 }
811
812 void
814 {
815 using namespace jtx;
816
817 const char credType[] = "abcde";
818 Account const issuer{"issuer"};
819 Account const alice{"alice"};
820 Account const bob{"bob"};
821 Account const maria{"maria"};
822 Account const john{"john"};
823
824 {
825 testcase("Payment failed with disabled credentials rule.");
826
827 Env env(*this, supported_amendments() - featureCredentials);
828
829 env.fund(XRP(5000), issuer, bob, alice);
830 env.close();
831
832 // Bob require preauthorization
833 env(fset(bob, asfDepositAuth));
834 env.close();
835
836 // Setup DepositPreauth object failed - amendent is not supported
837 env(deposit::authCredentials(bob, {{issuer, credType}}),
839 env.close();
840
841 // But can create old DepositPreauth
842 env(deposit::auth(bob, alice));
843 env.close();
844
845 // And alice can't pay with any credentials, amendement is not
846 // enabled
847 std::string const invalidIdx =
848 "0E0B04ED60588A758B67E21FBBE95AC5A63598BA951761DC0EC9C08D7E"
849 "01E034";
850 env(pay(alice, bob, XRP(10)),
851 credentials::ids({invalidIdx}),
853 env.close();
854 }
855
856 {
857 testcase("Payment with credentials.");
858
859 Env env(*this);
860
861 env.fund(XRP(5000), issuer, alice, bob, john);
862 env.close();
863
864 // Issuer create credentials, but Alice didn't accept them yet
865 env(credentials::create(alice, issuer, credType));
866 env.close();
867
868 // Get the index of the credentials
869 auto const jv =
870 credentials::ledgerEntry(env, alice, issuer, credType);
871 std::string const credIdx = jv[jss::result][jss::index].asString();
872
873 // Bob require preauthorization
874 env(fset(bob, asfDepositAuth));
875 env.close();
876
877 // Bob will accept payements from accounts with credentials signed
878 // by 'issuer'
879 env(deposit::authCredentials(bob, {{issuer, credType}}));
880 env.close();
881
882 auto const jDP =
883 ledgerEntryDepositPreauth(env, bob, {{issuer, credType}});
884 BEAST_EXPECT(
885 jDP.isObject() && jDP.isMember(jss::result) &&
886 !jDP[jss::result].isMember(jss::error) &&
887 jDP[jss::result].isMember(jss::node) &&
888 jDP[jss::result][jss::node].isMember("LedgerEntryType") &&
889 jDP[jss::result][jss::node]["LedgerEntryType"] ==
890 jss::DepositPreauth);
891
892 // Alice can't pay - empty credentials array
893 {
894 auto jv = pay(alice, bob, XRP(100));
895 jv[sfCredentialIDs.jsonName] = Json::arrayValue;
896 env(jv, ter(temMALFORMED));
897 env.close();
898 }
899
900 // Alice can't pay - not accepted credentials
901 env(pay(alice, bob, XRP(100)),
902 credentials::ids({credIdx}),
904 env.close();
905
906 // Alice accept the credentials
907 env(credentials::accept(alice, issuer, credType));
908 env.close();
909
910 // Now Alice can pay
911 env(pay(alice, bob, XRP(100)), credentials::ids({credIdx}));
912 env.close();
913
914 // Alice can pay Maria without depositPreauth enabled
915 env(pay(alice, maria, XRP(250)), credentials::ids({credIdx}));
916 env.close();
917
918 // john can accept payment with old depositPreauth and valid
919 // credentials
920 env(fset(john, asfDepositAuth));
921 env(deposit::auth(john, alice));
922 env(pay(alice, john, XRP(100)), credentials::ids({credIdx}));
923 env.close();
924 }
925
926 {
927 testcase("Payment failed with invalid credentials.");
928
929 Env env(*this);
930
931 env.fund(XRP(10000), issuer, alice, bob, maria);
932 env.close();
933
934 // Issuer create credentials, but Alice didn't accept them yet
935 env(credentials::create(alice, issuer, credType));
936 env.close();
937 // Alice accept the credentials
938 env(credentials::accept(alice, issuer, credType));
939 env.close();
940 // Get the index of the credentials
941 auto const jv =
942 credentials::ledgerEntry(env, alice, issuer, credType);
943 std::string const credIdx = jv[jss::result][jss::index].asString();
944
945 {
946 // Success as destination didn't enable preauthorization so
947 // valid credentials will not fail
948 env(pay(alice, bob, XRP(100)), credentials::ids({credIdx}));
949 }
950
951 // Bob require preauthorization
952 env(fset(bob, asfDepositAuth));
953 env.close();
954
955 {
956 // Fail as destination didn't setup DepositPreauth object
957 env(pay(alice, bob, XRP(100)),
958 credentials::ids({credIdx}),
960 }
961
962 // Bob setup DepositPreauth object, duplicates is not allowed
964 bob, {{issuer, credType}, {issuer, credType}}),
966
967 // Bob setup DepositPreauth object
968 env(deposit::authCredentials(bob, {{issuer, credType}}));
969 env.close();
970
971 {
972 std::string const invalidIdx =
973 "0E0B04ED60588A758B67E21FBBE95AC5A63598BA951761DC0EC9C08D7E"
974 "01E034";
975 // Alice can't pay with non-existing credentials
976 env(pay(alice, bob, XRP(100)),
977 credentials::ids({invalidIdx}),
979 }
980
981 { // maria can't pay using valid credentials but issued for
982 // different account
983 env(pay(maria, bob, XRP(100)),
984 credentials::ids({credIdx}),
986 }
987
988 {
989 // create another valid credential
990 const char credType2[] = "fghij";
991 env(credentials::create(alice, issuer, credType2));
992 env.close();
993 env(credentials::accept(alice, issuer, credType2));
994 env.close();
995 auto const jv =
996 credentials::ledgerEntry(env, alice, issuer, credType2);
997 std::string const credIdx2 =
998 jv[jss::result][jss::index].asString();
999
1000 // Alice can't pay with invalid set of valid credentials
1001 env(pay(alice, bob, XRP(100)),
1002 credentials::ids({credIdx, credIdx2}),
1004 }
1005
1006 // Error, duplicate credentials
1007 env(pay(alice, bob, XRP(100)),
1008 credentials::ids({credIdx, credIdx}),
1009 ter(temMALFORMED));
1010
1011 // Alice can pay
1012 env(pay(alice, bob, XRP(100)), credentials::ids({credIdx}));
1013 env.close();
1014 }
1015 }
1016
1017 void
1019 {
1020 using namespace jtx;
1021
1022 const char credType[] = "abcde";
1023 Account const issuer{"issuer"};
1024 Account const alice{"alice"};
1025 Account const bob{"bob"};
1026 Account const maria{"maria"};
1027
1028 {
1029 testcase("Creating / deleting with credentials.");
1030
1031 Env env(*this);
1032
1033 env.fund(XRP(5000), issuer, alice, bob);
1034 env.close();
1035
1036 {
1037 // both included [AuthorizeCredentials UnauthorizeCredentials]
1038 auto jv = deposit::authCredentials(bob, {{issuer, credType}});
1039 jv[sfUnauthorizeCredentials.jsonName] = Json::arrayValue;
1040 env(jv, ter(temMALFORMED));
1041 }
1042
1043 {
1044 // both included [Unauthorize, AuthorizeCredentials]
1045 auto jv = deposit::authCredentials(bob, {{issuer, credType}});
1046 jv[sfUnauthorize.jsonName] = issuer.human();
1047 env(jv, ter(temMALFORMED));
1048 }
1049
1050 {
1051 // both included [Authorize, AuthorizeCredentials]
1052 auto jv = deposit::authCredentials(bob, {{issuer, credType}});
1053 jv[sfAuthorize.jsonName] = issuer.human();
1054 env(jv, ter(temMALFORMED));
1055 }
1056
1057 {
1058 // both included [Unauthorize, UnauthorizeCredentials]
1059 auto jv = deposit::unauthCredentials(bob, {{issuer, credType}});
1060 jv[sfUnauthorize.jsonName] = issuer.human();
1061 env(jv, ter(temMALFORMED));
1062 }
1063
1064 {
1065 // both included [Authorize, UnauthorizeCredentials]
1066 auto jv = deposit::unauthCredentials(bob, {{issuer, credType}});
1067 jv[sfAuthorize.jsonName] = issuer.human();
1068 env(jv, ter(temMALFORMED));
1069 }
1070
1071 {
1072 // AuthorizeCredentials is empty
1073 auto jv = deposit::authCredentials(bob, {});
1074 env(jv, ter(temARRAY_EMPTY));
1075 }
1076
1077 {
1078 // invalid issuer
1079 auto jv = deposit::authCredentials(bob, {});
1080 auto& arr(jv[sfAuthorizeCredentials.jsonName]);
1082 cred[jss::Issuer] = to_string(xrpAccount());
1083 cred[sfCredentialType.jsonName] =
1084 strHex(std::string_view(credType));
1085 Json::Value credParent;
1086 credParent[jss::Credential] = cred;
1087 arr.append(std::move(credParent));
1088
1089 env(jv, ter(temINVALID_ACCOUNT_ID));
1090 }
1091
1092 {
1093 // empty credential type
1094 auto jv = deposit::authCredentials(bob, {{issuer, {}}});
1095 env(jv, ter(temMALFORMED));
1096 }
1097
1098 {
1099 // AuthorizeCredentials is larger than 8 elements
1100 Account const a("a"), b("b"), c("c"), d("d"), e("e"), f("f"),
1101 g("g"), h("h"), i("i");
1102 auto const& z = credType;
1103 auto jv = deposit::authCredentials(
1104 bob,
1105 {{a, z},
1106 {b, z},
1107 {c, z},
1108 {d, z},
1109 {e, z},
1110 {f, z},
1111 {g, z},
1112 {h, z},
1113 {i, z}});
1114 env(jv, ter(temARRAY_TOO_LARGE));
1115 }
1116
1117 {
1118 // Can't create with non-existing issuer
1119 Account const rick{"rick"};
1120 auto jv = deposit::authCredentials(bob, {{rick, credType}});
1121 env(jv, ter(tecNO_ISSUER));
1122 env.close();
1123 }
1124
1125 {
1126 // not enough reserve
1127 Account const john{"john"};
1128 env.fund(env.current()->fees().accountReserve(0), john);
1129 auto jv = deposit::authCredentials(john, {{issuer, credType}});
1130 env(jv, ter(tecINSUFFICIENT_RESERVE));
1131 }
1132
1133 {
1134 // NO deposit object exists
1135 env(deposit::unauthCredentials(bob, {{issuer, credType}}),
1136 ter(tecNO_ENTRY));
1137 }
1138
1139 // Create DepositPreauth object
1140 {
1141 env(deposit::authCredentials(bob, {{issuer, credType}}));
1142 env.close();
1143
1144 auto const jDP =
1145 ledgerEntryDepositPreauth(env, bob, {{issuer, credType}});
1146 BEAST_EXPECT(
1147 jDP.isObject() && jDP.isMember(jss::result) &&
1148 !jDP[jss::result].isMember(jss::error) &&
1149 jDP[jss::result].isMember(jss::node) &&
1150 jDP[jss::result][jss::node].isMember("LedgerEntryType") &&
1151 jDP[jss::result][jss::node]["LedgerEntryType"] ==
1152 jss::DepositPreauth);
1153
1154 // Check object fields
1155 BEAST_EXPECT(
1156 jDP[jss::result][jss::node][jss::Account] == bob.human());
1157 auto const& credentials(
1158 jDP[jss::result][jss::node]["AuthorizeCredentials"]);
1159 BEAST_EXPECT(credentials.isArray() && credentials.size() == 1);
1160 for (auto const& o : credentials)
1161 {
1162 auto const& c(o[jss::Credential]);
1163 BEAST_EXPECT(c[jss::Issuer].asString() == issuer.human());
1164 BEAST_EXPECT(
1165 c["CredentialType"].asString() ==
1166 strHex(std::string_view(credType)));
1167 }
1168
1169 // can't create duplicate
1170 env(deposit::authCredentials(bob, {{issuer, credType}}),
1171 ter(tecDUPLICATE));
1172 }
1173
1174 // Delete DepositPreauth object
1175 {
1176 env(deposit::unauthCredentials(bob, {{issuer, credType}}));
1177 env.close();
1178 auto const jDP =
1179 ledgerEntryDepositPreauth(env, bob, {{issuer, credType}});
1180 BEAST_EXPECT(
1181 jDP.isObject() && jDP.isMember(jss::result) &&
1182 jDP[jss::result].isMember(jss::error) &&
1183 jDP[jss::result][jss::error] == "entryNotFound");
1184 }
1185 }
1186 }
1187
1188 void
1190 {
1191 using namespace jtx;
1192 const char credType[] = "abcde";
1193 const char credType2[] = "fghijkl";
1194 Account const issuer{"issuer"};
1195 Account const alice{"alice"};
1196 Account const bob{"bob"};
1197 Account const gw{"gw"};
1198 IOU const USD = gw["USD"];
1199 Account const zelda{"zelda"};
1200
1201 {
1202 testcase("Payment failed with expired credentials.");
1203
1204 Env env(*this);
1205
1206 env.fund(XRP(10000), issuer, alice, bob, gw);
1207 env.close();
1208
1209 // Create credentials
1210 auto jv = credentials::create(alice, issuer, credType);
1211 // Current time in ripple epoch.
1212 // Every time ledger close, unittest timer increase by 10s
1213 uint32_t const t = env.current()
1214 ->info()
1215 .parentCloseTime.time_since_epoch()
1216 .count() +
1217 60;
1218 jv[sfExpiration.jsonName] = t;
1219 env(jv);
1220 env.close();
1221
1222 // Alice accept the credentials
1223 env(credentials::accept(alice, issuer, credType));
1224 env.close();
1225
1226 // Create credential which not expired
1227 jv = credentials::create(alice, issuer, credType2);
1228 uint32_t const t2 = env.current()
1229 ->info()
1230 .parentCloseTime.time_since_epoch()
1231 .count() +
1232 1000;
1233 jv[sfExpiration.jsonName] = t2;
1234 env(jv);
1235 env.close();
1236 env(credentials::accept(alice, issuer, credType2));
1237 env.close();
1238
1239 BEAST_EXPECT(ownerCount(env, issuer) == 0);
1240 BEAST_EXPECT(ownerCount(env, alice) == 2);
1241
1242 // Get the index of the credentials
1243 jv = credentials::ledgerEntry(env, alice, issuer, credType);
1244 std::string const credIdx = jv[jss::result][jss::index].asString();
1245 jv = credentials::ledgerEntry(env, alice, issuer, credType2);
1246 std::string const credIdx2 = jv[jss::result][jss::index].asString();
1247
1248 // Bob require preauthorization
1249 env(fset(bob, asfDepositAuth));
1250 env.close();
1251 // Bob setup DepositPreauth object
1253 bob, {{issuer, credType}, {issuer, credType2}}));
1254 env.close();
1255
1256 {
1257 // Alice can pay
1258 env(pay(alice, bob, XRP(100)),
1259 credentials::ids({credIdx, credIdx2}));
1260 env.close();
1261 env.close();
1262
1263 // Ledger closed, time increased, alice can't pay anymore
1264 env(pay(alice, bob, XRP(100)),
1265 credentials::ids({credIdx, credIdx2}),
1266 ter(tecEXPIRED));
1267 env.close();
1268
1269 {
1270 // check that expired credentials were deleted
1271 auto const jDelCred =
1272 credentials::ledgerEntry(env, alice, issuer, credType);
1273 BEAST_EXPECT(
1274 jDelCred.isObject() && jDelCred.isMember(jss::result) &&
1275 jDelCred[jss::result].isMember(jss::error) &&
1276 jDelCred[jss::result][jss::error] == "entryNotFound");
1277 }
1278
1279 {
1280 // check that non-expired credential still present
1281 auto const jle =
1282 credentials::ledgerEntry(env, alice, issuer, credType2);
1283 BEAST_EXPECT(
1284 jle.isObject() && jle.isMember(jss::result) &&
1285 !jle[jss::result].isMember(jss::error) &&
1286 jle[jss::result].isMember(jss::node) &&
1287 jle[jss::result][jss::node].isMember(
1288 "LedgerEntryType") &&
1289 jle[jss::result][jss::node]["LedgerEntryType"] ==
1290 jss::Credential &&
1291 jle[jss::result][jss::node][jss::Issuer] ==
1292 issuer.human() &&
1293 jle[jss::result][jss::node][jss::Subject] ==
1294 alice.human() &&
1295 jle[jss::result][jss::node]["CredentialType"] ==
1296 strHex(std::string_view(credType2)));
1297 }
1298
1299 BEAST_EXPECT(ownerCount(env, issuer) == 0);
1300 BEAST_EXPECT(ownerCount(env, alice) == 1);
1301 }
1302
1303 {
1304 auto jv = credentials::create(gw, issuer, credType);
1305 uint32_t const t = env.current()
1306 ->info()
1307 .parentCloseTime.time_since_epoch()
1308 .count() +
1309 40;
1310 jv[sfExpiration.jsonName] = t;
1311 env(jv);
1312 env.close();
1313 env(credentials::accept(gw, issuer, credType));
1314 env.close();
1315
1316 jv = credentials::ledgerEntry(env, gw, issuer, credType);
1317 std::string const credIdx =
1318 jv[jss::result][jss::index].asString();
1319
1320 BEAST_EXPECT(ownerCount(env, issuer) == 0);
1321 BEAST_EXPECT(ownerCount(env, gw) == 1);
1322
1323 env.close();
1324 env.close();
1325 env.close();
1326
1327 // credentials are expired
1328 env(pay(gw, bob, USD(150)),
1329 credentials::ids({credIdx}),
1330 ter(tecEXPIRED));
1331 env.close();
1332
1333 // check that expired credentials were deleted
1334 auto const jDelCred =
1335 credentials::ledgerEntry(env, gw, issuer, credType);
1336 BEAST_EXPECT(
1337 jDelCred.isObject() && jDelCred.isMember(jss::result) &&
1338 jDelCred[jss::result].isMember(jss::error) &&
1339 jDelCred[jss::result][jss::error] == "entryNotFound");
1340
1341 BEAST_EXPECT(ownerCount(env, issuer) == 0);
1342 BEAST_EXPECT(ownerCount(env, gw) == 0);
1343 }
1344 }
1345
1346 {
1347 using namespace std::chrono;
1348
1349 testcase("Escrow failed with expired credentials.");
1350
1351 Env env(*this);
1352
1353 env.fund(XRP(5000), issuer, alice, bob, zelda);
1354 env.close();
1355
1356 // Create credentials
1357 auto jv = credentials::create(zelda, issuer, credType);
1358 uint32_t const t = env.current()
1359 ->info()
1360 .parentCloseTime.time_since_epoch()
1361 .count() +
1362 50;
1363 jv[sfExpiration.jsonName] = t;
1364 env(jv);
1365 env.close();
1366
1367 // Zelda accept the credentials
1368 env(credentials::accept(zelda, issuer, credType));
1369 env.close();
1370
1371 // Get the index of the credentials
1372 jv = credentials::ledgerEntry(env, zelda, issuer, credType);
1373 std::string const credIdx = jv[jss::result][jss::index].asString();
1374
1375 // Bob require preauthorization
1376 env(fset(bob, asfDepositAuth));
1377 env.close();
1378 // Bob setup DepositPreauth object
1379 env(deposit::authCredentials(bob, {{issuer, credType}}));
1380 env.close();
1381
1382 auto const seq = env.seq(alice);
1383 env(escrow(alice, bob, XRP(1000)), finish_time(env.now() + 1s));
1384 env.close();
1385
1386 // zelda can't finish escrow with invalid credentials
1387 {
1388 env(finish(zelda, alice, seq),
1389 credentials::ids({}),
1390 ter(temMALFORMED));
1391 env.close();
1392 }
1393
1394 {
1395 // zelda can't finish escrow with invalid credentials
1396 std::string const invalidIdx =
1397 "0E0B04ED60588A758B67E21FBBE95AC5A63598BA951761DC0EC9C08D7E"
1398 "01E034";
1399
1400 env(finish(zelda, alice, seq),
1401 credentials::ids({invalidIdx}),
1403 env.close();
1404 }
1405
1406 { // Ledger closed, time increased, zelda can't finish escrow
1407 env(finish(zelda, alice, seq),
1408 credentials::ids({credIdx}),
1409 fee(1500),
1410 ter(tecEXPIRED));
1411 env.close();
1412 }
1413
1414 // check that expired credentials were deleted
1415 auto const jDelCred =
1416 credentials::ledgerEntry(env, zelda, issuer, credType);
1417 BEAST_EXPECT(
1418 jDelCred.isObject() && jDelCred.isMember(jss::result) &&
1419 jDelCred[jss::result].isMember(jss::error) &&
1420 jDelCred[jss::result][jss::error] == "entryNotFound");
1421 }
1422 }
1423
1424 void
1426 {
1427 using namespace jtx;
1428
1429 Account const stock{"stock"};
1430 Account const alice{"alice"};
1431 Account const bob{"bob"};
1432
1433 Env env(*this);
1434
1435 testcase("Sorting credentials.");
1436
1437 env.fund(XRP(5000), stock, alice, bob);
1438
1440 {"a", "a"},
1441 {"b", "b"},
1442 {"c", "c"},
1443 {"d", "d"},
1444 {"e", "e"},
1445 {"f", "f"},
1446 {"g", "g"},
1447 {"h", "h"}};
1448
1449 for (auto const& c : credentials)
1450 env.fund(XRP(5000), c.issuer);
1451 env.close();
1452
1454 std::mt19937 gen(rd());
1455
1456 {
1458 for (auto const& c : credentials)
1459 pubKey2Acc.emplace(c.issuer.human(), c.issuer);
1460
1461 // check sorting in object
1462 for (int i = 0; i < 10; ++i)
1463 {
1464 std::ranges::shuffle(credentials, gen);
1465 env(deposit::authCredentials(stock, credentials));
1466 env.close();
1467
1468 auto const dp =
1469 ledgerEntryDepositPreauth(env, stock, credentials);
1470 auto const& authCred(
1471 dp[jss::result][jss::node]["AuthorizeCredentials"]);
1472 BEAST_EXPECT(
1473 authCred.isArray() &&
1474 authCred.size() == credentials.size());
1476 for (auto const& o : authCred)
1477 {
1478 auto const& c(o[jss::Credential]);
1479 auto issuer = c[jss::Issuer].asString();
1480
1481 if (BEAST_EXPECT(pubKey2Acc.contains(issuer)))
1482 readedCreds.emplace_back(
1483 pubKey2Acc.at(issuer),
1484 c["CredentialType"].asString());
1485 }
1486
1487 BEAST_EXPECT(std::ranges::is_sorted(readedCreds));
1488
1489 env(deposit::unauthCredentials(stock, credentials));
1490 env.close();
1491 }
1492 }
1493
1494 {
1495 std::ranges::shuffle(credentials, gen);
1496 env(deposit::authCredentials(stock, credentials));
1497 env.close();
1498
1499 // check sorting in params
1500 for (int i = 0; i < 10; ++i)
1501 {
1502 std::ranges::shuffle(credentials, gen);
1503 env(deposit::authCredentials(stock, credentials),
1504 ter(tecDUPLICATE));
1505 }
1506 }
1507
1508 testcase("Check duplicate credentials.");
1509 {
1510 // check duplicates in depositPreauth params
1512 credentials.begin(), credentials.end() - 1);
1513
1514 std::ranges::shuffle(copyCredentials, gen);
1515 for (auto const& c : copyCredentials)
1516 {
1517 auto credentials2 = copyCredentials;
1518 credentials2.push_back(c);
1519 env(deposit::authCredentials(stock, credentials2),
1520 ter(temMALFORMED));
1521 }
1522
1523 // create batch of credentials and save their hashes
1524 std::vector<std::string> credentialIDs;
1525 for (auto const& c : credentials)
1526 {
1527 env(credentials::create(alice, c.issuer, c.credType));
1528 env.close();
1529 env(credentials::accept(alice, c.issuer, c.credType));
1530 env.close();
1531
1532 credentialIDs.push_back(credentials::ledgerEntry(
1533 env,
1534 alice,
1535 c.issuer,
1536 c.credType)[jss::result][jss::index]
1537 .asString());
1538 }
1539
1540 // check duplicates in payment params
1541 for (auto const& h : credentialIDs)
1542 {
1543 auto credentialIDs2 = credentialIDs;
1544 credentialIDs2.push_back(h);
1545
1546 env(pay(alice, bob, XRP(100)),
1547 credentials::ids(credentialIDs2),
1548 ter(temMALFORMED));
1549 }
1550 }
1551 }
1552
1553 void
1554 run() override
1555 {
1556 testEnable();
1557 testInvalid();
1558 auto const supported{jtx::supported_amendments()};
1559 testPayment(supported - featureDepositPreauth - featureCredentials);
1560 testPayment(supported - featureDepositPreauth);
1561 testPayment(supported - featureCredentials);
1562 testPayment(supported);
1567 }
1568};
1569
1570BEAST_DEFINE_TESTSUITE(DepositAuth, app, ripple);
1571BEAST_DEFINE_TESTSUITE(DepositPreauth, app, ripple);
1572
1573} // namespace test
1574} // namespace ripple
T at(T... args)
T begin(T... args)
Represents a JSON value.
Definition: json_value.h:148
Value & append(const Value &value)
Append value to array at the end.
Definition: json_value.cpp:897
Value removeMember(const char *key)
Remove and return the named member.
Definition: json_value.cpp:922
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:475
A testsuite class.
Definition: suite.h:55
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:155
bool expect(Condition const &shouldBeTrue)
Evaluate a test condition.
Definition: suite.h:229
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:120
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition: Env.cpp:212
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:532
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:328
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:117
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:264
NetClock::time_point now()
Returns the current network time.
Definition: Env.h:281
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:767
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition: Env.cpp:179
void memoize(Account const &account)
Associate AccountID with account.
Definition: Env.cpp:152
std::shared_ptr< SLE const > le(Account const &account) const
Return an account root.
Definition: Env.cpp:221
Converts to IOU Issue or STAmount.
Set the fee on a JTx.
Definition: fee.h:37
Match set account flags.
Definition: flags.h:113
Match clear account flags.
Definition: flags.h:130
Match the number of items in the account's owner directory.
Definition: owners.h:73
Add a path.
Definition: paths.h:58
Check a set of conditions.
Definition: require.h:65
Sets the SendMax on a JTx.
Definition: sendmax.h:33
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition: ter.h:35
Set a ticket sequence on a JTx.
Definition: ticket.h:48
Set the flags on a JTx.
Definition: txflags.h:31
T contains(T... args)
T emplace_back(T... args)
T emplace(T... args)
T end(T... args)
@ arrayValue
array value (ordered list)
Definition: json_value.h:43
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:44
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:32
Json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:50
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:83
Json::Value unauth(Account const &account, Account const &unauth)
Remove preauthorization for deposit.
Definition: deposit.cpp:43
Json::Value auth(Account const &account, Account const &auth)
Preauthorize for deposit.
Definition: deposit.cpp:32
Json::Value unauthCredentials(jtx::Account const &account, std::vector< AuthorizeCredentials > const &auth)
Definition: deposit.cpp:74
Json::Value authCredentials(jtx::Account const &account, std::vector< AuthorizeCredentials > const &auth)
Definition: deposit.cpp:54
Json::Value create(Account const &account, std::uint32_t count)
Create one of more tickets.
Definition: ticket.cpp:31
std::uint32_t ownerCount(Env const &env, Account const &account)
Definition: TestHelpers.cpp:54
Json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
Definition: flags.h:41
Json::Value escrow(AccountID const &account, AccountID const &to, STAmount const &amount)
owner_count< ltOFFER > offers
Match the number of offers in the account's owner directory.
Definition: owners.h:92
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition: trust.cpp:32
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition: flags.cpp:29
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:30
Json::Value noop(Account const &account)
The null transaction.
Definition: noop.h:31
Json::Value finish(AccountID const &account, AccountID const &from, std::uint32_t seq)
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition: offer.cpp:29
owner_count< ltTICKET > tickets
Match the number of tickets on the account.
Definition: ticket.h:64
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
FeatureBitset supported_amendments()
Definition: Env.h:73
static Json::Value ledgerEntryDepositPreauth(jtx::Env &env, jtx::Account const &acc, std::vector< jtx::deposit::AuthorizeCredentials > const &auth)
static bool hasDepositAuth(jtx::Env const &env, jtx::Account const &acct)
static XRPAmount reserve(jtx::Env &env, std::uint32_t count)
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
AccountID const & xrpAccount()
Compute AccountID from public key.
Definition: AccountID.cpp:178
@ lsfDepositAuth
constexpr std::uint32_t tfPassive
Definition: TxFlags.h:96
std::string strHex(FwdIt begin, FwdIt end)
Definition: strHex.h:30
@ tecNO_ENTRY
Definition: TER.h:293
@ tecNO_ISSUER
Definition: TER.h:286
@ tecNO_TARGET
Definition: TER.h:291
@ tecDUPLICATE
Definition: TER.h:302
@ tecBAD_CREDENTIALS
Definition: TER.h:346
@ tecNO_PERMISSION
Definition: TER.h:292
@ tecPATH_DRY
Definition: TER.h:281
@ tecINSUFFICIENT_RESERVE
Definition: TER.h:294
@ tecEXPIRED
Definition: TER.h:301
@ tesSUCCESS
Definition: TER.h:242
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
constexpr std::uint32_t tfSell
Definition: TxFlags.h:99
@ terINSUF_FEE_B
Definition: TER.h:216
@ terNO_ACCOUNT
Definition: TER.h:217
TERSubset< CanCvtToTER > TER
Definition: TER.h:627
constexpr std::uint32_t tfSetNoRipple
Definition: TxFlags.h:113
@ temBAD_FEE
Definition: TER.h:92
@ temMALFORMED
Definition: TER.h:87
@ temINVALID_FLAG
Definition: TER.h:111
@ temARRAY_EMPTY
Definition: TER.h:140
@ temARRAY_TOO_LARGE
Definition: TER.h:141
@ temDISABLED
Definition: TER.h:114
@ temCANNOT_PREAUTH_SELF
Definition: TER.h:120
@ temINVALID_ACCOUNT_ID
Definition: TER.h:119
T push_back(T... args)
void run() override
Runs the suite.
void testPayment(FeatureBitset features)
void run() override
Runs the suite.
Represents an XRP or IOU quantity This customizes the string conversion and supports XRP conversions ...
Set the "FinishAfter" time tag on a JTx.
Definition: TestHelpers.h:268
Set the sequence number on a JTx.
Definition: seq.h:34