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