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