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