xrpld
Loading...
Searching...
No Matches
Sponsor_test.cpp
1#include <test/jtx/Account.h>
2#include <test/jtx/Env.h>
3#include <test/jtx/TestHelpers.h>
4#include <test/jtx/acctdelete.h>
5#include <test/jtx/amount.h>
6#include <test/jtx/batch.h>
7#include <test/jtx/check.h>
8#include <test/jtx/credentials.h>
9#include <test/jtx/delegate.h>
10#include <test/jtx/deposit.h>
11#include <test/jtx/did.h>
12#include <test/jtx/escrow.h>
13#include <test/jtx/fee.h>
14#include <test/jtx/flags.h>
15#include <test/jtx/ledgerStateFix.h>
16#include <test/jtx/mpt.h>
17#include <test/jtx/multisign.h>
18#include <test/jtx/noop.h>
19#include <test/jtx/offer.h>
20#include <test/jtx/paths.h>
21#include <test/jtx/pay.h>
22#include <test/jtx/sendmax.h>
23#include <test/jtx/seq.h>
24#include <test/jtx/sig.h>
25#include <test/jtx/sponsor.h>
26#include <test/jtx/ter.h>
27#include <test/jtx/ticket.h>
28#include <test/jtx/token.h>
29#include <test/jtx/trust.h>
30#include <test/jtx/txflags.h>
31#include <test/jtx/vault.h>
32
33#include <xrpl/basics/Number.h>
34#include <xrpl/basics/Slice.h>
35#include <xrpl/basics/base_uint.h>
36#include <xrpl/basics/chrono.h>
37#include <xrpl/basics/strHex.h>
38#include <xrpl/beast/unit_test/suite.h>
39#include <xrpl/beast/utility/instrumentation.h>
40#include <xrpl/core/ServiceRegistry.h>
41#include <xrpl/json/json_value.h>
42#include <xrpl/ledger/ApplyView.h>
43#include <xrpl/ledger/OpenView.h>
44#include <xrpl/protocol/AccountID.h>
45#include <xrpl/protocol/Asset.h>
46#include <xrpl/protocol/Feature.h>
47#include <xrpl/protocol/Indexes.h>
48#include <xrpl/protocol/Issue.h>
49#include <xrpl/protocol/LedgerFormats.h>
50#include <xrpl/protocol/SField.h>
51#include <xrpl/protocol/STAmount.h>
52#include <xrpl/protocol/SeqProxy.h>
53#include <xrpl/protocol/TER.h>
54#include <xrpl/protocol/TxFlags.h>
55#include <xrpl/protocol/UintTypes.h>
56#include <xrpl/protocol/jss.h>
57#include <xrpl/tx/apply.h>
58#include <xrpl/tx/applySteps.h>
59
60#include <cstdint>
61#include <functional>
62#include <limits>
63#include <memory>
64#include <optional>
65#include <set>
66#include <string>
67#include <utility>
68#include <vector>
69
70namespace xrpl::test {
71
72static STAmount
74{
75 return env.current()->fees().reserve * count;
76}
77
78static STAmount
80{
81 return env.current()->fees().accountReserve(count, 1);
82}
83
84static void
85adjustAccountXRPBalance(jtx::Env& env, jtx::Account const& account, STAmount const& balanceTo)
86{
87 using namespace test::jtx;
88 XRPL_ASSERT(isXRP(balanceTo), "adjustAccountXRPBalance: balanceTo must be XRP");
89 auto const currentBalance = env.balance(account);
90 if (currentBalance == balanceTo)
91 return;
92
93 auto const baseFee = env.current()->fees().base;
94 if (currentBalance > balanceTo)
95 {
96 env(pay(account, env.master, currentBalance - balanceTo),
97 Fee(XRP(1)),
99 Sig(sfSponsorSignature, env.master));
100 }
101 else
102 {
103 env(pay(env.master, account, balanceTo - currentBalance), Fee(baseFee));
104 }
105
106 env.close();
107}
108
110{
111public:
112 void
114 {
115 testcase("Disabled");
116 using namespace test::jtx;
117 Env env{*this, testableAmendments() - featureSponsor};
118 Account const alice("alice");
119 Account const sponsor("sponsor");
120 env.fund(XRP(10000), alice, sponsor);
121
122 // check Sponsor fields
123 auto const jt = noop(alice);
124 auto jt1 = jt;
125 jt1[sfSponsor.jsonName] = sponsor.human();
126 env(jt1, Ter(temDISABLED));
127 env(jt, Sig(sfSponsorSignature, sponsor), Ter(temDISABLED));
128
129 auto jt2 = jt;
130 jt2[sfSponsorFlags.jsonName] = spfSponsorFee | spfSponsorReserve;
131 env(jt2, Ter(temDISABLED));
132
133 // check Sponsor transactions
134 env(sponsor::transfer(alice, 0), Ter(temDISABLED));
136 }
137
138 void
140 {
141 testcase("Invalid SponsorshipSet");
142 using namespace test::jtx;
143 Env env{*this, testableAmendments()};
144 Account const alice("alice");
145 Account const bob("bob");
146 Account const sponsor("sponsor");
147 Account const noFunded("noFunded");
148 Account const gw("gw");
149
150 auto const usd = gw["usd"];
151 env.fund(XRP(10000), alice, sponsor, gw);
152 env.close();
153
154 //
155 // preflight
156 //
157
158 // Invalid flags
159 {
160 env(sponsor::set(sponsor, ~tfSponsorshipSetMask - tfInnerBatchTxn),
161 sponsor::SponseeAcc(alice),
163
164 env(sponsor::set(
165 sponsor,
166 tfSponsorshipSetRequireSignForFee | tfSponsorshipClearRequireSignForFee),
167 sponsor::SponseeAcc(alice),
169
170 env(sponsor::set(
171 sponsor,
172 tfSponsorshipSetRequireSignForReserve |
173 tfSponsorshipClearRequireSignForReserve),
174 sponsor::SponseeAcc(alice),
176
177 for (auto flag :
178 {tfSponsorshipSetRequireSignForFee,
179 tfSponsorshipClearRequireSignForFee,
180 tfSponsorshipSetRequireSignForReserve,
181 tfSponsorshipClearRequireSignForReserve})
182 {
183 env(sponsor::set(sponsor, tfDeleteObject | flag),
184 sponsor::SponseeAcc(alice),
186 }
187 }
188
189 // invalid SponsorAccount / Sponsee
190 // Account = Sponsor
191 env(sponsor::set(alice, tfDeleteObject),
194 // Account = Sponsee
195 env(sponsor::set(alice, tfDeleteObject), sponsor::SponseeAcc(alice), Ter(temMALFORMED));
196 // Both Sponsor and Sponsee are specified
197 env(sponsor::set(alice, 0),
199 sponsor::SponseeAcc(alice),
201
202 // Invalid FeeAmountDelta
203 for (auto const& amt : {XRP(0), usd(1)})
204 {
205 env(sponsor::set_fee(sponsor, 0, amt, XRP(1)),
206 sponsor::SponseeAcc(alice),
208 }
209 // Invalid MaxFee
210 for (auto const& amt : {XRP(-1), usd(1)})
211 {
212 env(sponsor::set_fee(sponsor, 0, XRP(1), amt),
213 sponsor::SponseeAcc(alice),
215 }
216 // Invalid RemainingOwnerCountDelta
217 env(sponsor::set(sponsor, 0, 0, XRP(2), XRP(1)),
218 sponsor::SponseeAcc(alice),
219 Ter(temINVALID));
220
221 // Invalid Delete operation
222 env(sponsor::set_reserve(sponsor, tfDeleteObject, 1),
223 sponsor::SponseeAcc(alice),
225 env(sponsor::set_fee(sponsor, tfDeleteObject, XRP(1)),
226 sponsor::SponseeAcc(alice),
228 env(sponsor::set_max_fee(sponsor, tfDeleteObject, XRP(1)),
229 sponsor::SponseeAcc(alice),
231
232 // Invalid SponsorAccount with non-Delete operation
233 env(sponsor::set_reserve(sponsor, 0, 100),
236 env(sponsor::set_fee(sponsor, 0, XRP(1), XRP(1)),
239
240 // Redundant tx
242
243 //
244 // preclaim
245 //
246
247 // Invalid Sponsee
248 env(sponsor::set(sponsor, 0, 1), sponsor::SponseeAcc(noFunded), Ter(tecNO_DST));
249 env.close();
250
251 // Invalid Sponsor
252 env(sponsor::set(sponsor, tfDeleteObject),
254 Ter(tecNO_DST));
255 env.close();
256
257 // Invalid Delete operation (sponsorship not found)
258 env(sponsor::set(sponsor, tfDeleteObject), sponsor::SponseeAcc(alice), Ter(tecNO_ENTRY));
259 env.close();
260
261 // insufficient balance to sponsor Fee
262 adjustAccountXRPBalance(env, sponsor, env.current()->fees().reserve);
264 env.close();
265
266 // insufficient reserve to create sponsorship
267 adjustAccountXRPBalance(env, sponsor, XRP(100) + XRP(1) + reserve(env, 1) - drops(1));
268 env(sponsor::set(sponsor, 0, 100, XRP(100)),
269 sponsor::SponseeAcc(alice),
270 Fee(XRP(1)),
272 env.close();
273
274 // FeeAmount + Fee > Balance
275 // Balance = 1000XRP, FeeAmount = 1001XRP
277 env(sponsor::set_fee(sponsor, 0, XRP(1001)),
278 sponsor::SponseeAcc(alice),
279 Fee(XRP(1)),
281 env.close();
282 // Balance = 1000XRP, FeeAmount = 999XRP, Fee=2XRP
284 env(sponsor::set_fee(sponsor, 0, XRP(999)),
285 sponsor::SponseeAcc(alice),
286 Fee(XRP(2)),
288 env.close();
289
290 // create sponsor to use above tests
291 // need feeAmount(1000) + Fee(1) + reserve(~250) = ~1251
292 adjustAccountXRPBalance(env, sponsor, XRP(1000) + XRP(1) + reserve(env, 1));
293 env(sponsor::set(sponsor, 0, 100, XRP(1000)),
294 sponsor::SponseeAcc(alice),
295 Fee(XRP(1)),
296 Ter(tesSUCCESS));
297 env.close();
298
299 // delta-based balance check
300 // After create: sponsor balance ~ 0, feeAmount = XRP(1000)
301
302 // Decreasing feeAmount should succeed (refund, negative delta)
304 env(sponsor::set_fee(sponsor, 0, XRP(-200)),
305 sponsor::SponseeAcc(alice),
306 Fee(XRP(1)),
307 Ter(tesSUCCESS));
308 env.close();
309 // balance was 500, delta = 800-1000 = -200 (refund), balance = 500+200-1 = 699
310
311 // Increasing feeAmount within delta budget should succeed
313 env(sponsor::set_fee(sponsor, 0, XRP(50)),
314 sponsor::SponseeAcc(alice),
315 Fee(XRP(1)),
316 Ter(tesSUCCESS));
317 env.close();
318 // balance was 500, delta = 850-800 = 50, balance = 500-50-1 = 449
319
320 // Increasing feeAmount where delta exceeds balance should fail
322 env(sponsor::set_fee(sponsor, 0, XRP(350)),
323 sponsor::SponseeAcc(alice),
324 Fee(XRP(1)),
326 env.close();
327
328 // Increasing feeAmount to reach insufficient reserve
330 env(sponsor::set_fee(sponsor, 0, XRP(309)),
331 sponsor::SponseeAcc(alice),
332 Fee(XRP(1)),
334 env.close();
335 }
336
337 void
339 {
340 testcase("Pseudo account sponsorship");
341 using namespace test::jtx;
342 Env env{*this, testableAmendments()};
343 Account const alice("alice");
344 Account const bob("bob");
345 Account const gw("gw");
346 Account const sp("sponsor");
347
348 Asset const asset = gw["IOU"].asset();
349
350 env.fund(XRP(1000000), alice, bob, gw, sp);
351 env.close();
352
353 // Create a vault to get a pseudo account
354 Vault const vault{env};
355 auto [tx, keylet] = vault.create({.owner = alice, .asset = asset});
356 env(tx);
357 env.close();
358
359 auto const vaultSle = env.le(keylet);
360 BEAST_EXPECT(vaultSle);
361 Account const pseudoAcc("vault", vaultSle->getAccountID(sfAccount));
362 env.memoize(pseudoAcc);
363
364 // Sponsee is a pseudo account -> tecPSEUDO_ACCOUNT
365 env(sponsor::set(sp, 0, 100, XRP(100)),
366 sponsor::SponseeAcc(pseudoAcc),
368 env.close();
369
370 // Sponsor is a pseudo account -> tecPSEUDO_ACCOUNT
371 // (submitted by bob with counterpartySponsor pointing to pseudo account)
372 env(sponsor::set(bob, tfDeleteObject),
375 env.close();
376 }
377
378 void
380 {
381 testcase("Single signing");
382 using namespace test::jtx;
383 Env env{*this, testableAmendments()};
384 Account const alice("alice");
385 Account const sponsor("sponsor");
386 Account const invalid("invalid");
387
388 env.fund(XRP(10000), alice, sponsor);
389 env.close();
390
391 // Signature doesn't exist
392 auto tx = noop(alice);
393 tx[sfSponsor.jsonName] = sponsor.human();
394 tx[sfSponsorSignature.jsonName][sfSigningPubKey.jsonName] = strHex(sponsor.pk().slice());
395
397
398 // Invalid signature
399 tx[sfSponsorSignature.jsonName][sfTxnSignature.jsonName] = "DEADBEEF";
401
402 // Signer account doesn't exist
403 env(noop(alice),
404 Fee(XRP(1)),
406 Sig(sfSponsorSignature, invalid),
408
409 // Success
410 env(noop(alice),
411 Fee(XRP(1)),
413 Sig(sfSponsorSignature, sponsor),
414 Ter(tesSUCCESS));
415 }
416
417 void
419 {
420 testcase("Multi signing");
421 using namespace test::jtx;
422 Env env{*this, testableAmendments()};
423 Account const alice("alice");
424 Account const bob("bob");
425 Account const sponsor("sponsor");
426 Account const signer1("signer1");
427 Account const signer2("signer2");
428
429 env.fund(XRP(10000), alice, bob, sponsor);
430 env.close();
431
432 env(signers(sponsor, 1, {{signer1, 1}, {signer2, 1}}));
433 env.close();
434
435 // Invalid signature
436 auto tx = noop(alice);
437 auto& signers1 = tx[sfSponsorSignature.jsonName][sfSigners.jsonName][0U][sfSigner.jsonName];
438 signers1[sfAccount.jsonName] = signer1.human();
439 signers1[sfSigningPubKey.jsonName] = strHex(signer1.pk().slice());
440 signers1[sfTxnSignature.jsonName] = "DEADBEEF";
442
443 // bob is not a multi-signing account.
444 env(noop(alice),
445 Fee(XRP(1)),
447 Msig(sfSponsorSignature, {signer1}),
449
450 env(noop(alice),
451 Fee(XRP(1)),
453 Msig(sfSponsorSignature, {signer1}),
454 Ter(tesSUCCESS));
455 env.close();
456
457 env(signers(sponsor, 2, {{signer1, 1}, {signer2, 1}}));
458 env.close();
459
460 // test calculateBaseFee for multisigned sponsor
461 auto const baseFee = env.current()->fees().base;
462 env(noop(alice),
463 Fee(baseFee + 2 * baseFee - 1),
465 Msig(sfSponsorSignature, {signer1, signer2}),
467
468 env(noop(alice),
469 Fee(baseFee + 2 * baseFee),
471 Msig(sfSponsorSignature, {signer1, signer2}),
472 Ter(tesSUCCESS));
473 }
474
475 void
477 {
478 testcase("Invalid Sponsor Field");
479 using namespace test::jtx;
480 Env env{*this, testableAmendments()};
481 Account const alice("alice");
482 Account const sponsor("sponsor");
483 Account const noFunded("noFunded");
484 env.fund(XRP(10000), alice, sponsor);
485 env.close();
486
487 // Invalid Sponsor Account (Account = Sponsor.Account)
488 env(noop(alice), sponsor::As(alice, spfSponsorFee), Ter(temMALFORMED));
489
490 // Invalid Sponsor Account
491 // (SponsorSignature is specified but Sponsor.Account is not specified)
492 env(noop(alice), Sig(sfSponsorSignature, sponsor), Ter(temMALFORMED));
493
494 // Invalid Sponsor Account (Sponsor.Account doesn't exist)
495 env(noop(alice), sponsor::As(noFunded, spfSponsorReserve), Ter(terNO_ACCOUNT));
496 env(noop(alice),
498 Sig(sfSponsorSignature, noFunded),
500
501 // Invalid Flags
502 env(noop(alice),
505
506 // SponsorFlags=0 with valid sponsor (no sponsorship purpose)
507 env(noop(alice), sponsor::As(sponsor, 0), Ter(temINVALID_FLAG));
508
509 // no SponsorFlag with valid sponsor
510 auto tx = noop(alice);
511 tx[sfSponsor.jsonName] = sponsor.human();
512 env(tx, Ter(temINVALID_FLAG));
513
514 // Invalid Flags without sponsor
515 tx = noop(alice);
516 tx[sfSponsorFlags.jsonName] = spfSponsorFee | spfSponsorReserve;
517 env(tx, Ter(temINVALID_FLAG));
518 }
519
520 void
522 {
523 testcase("Simple SponsorshipSet");
524 using namespace test::jtx;
525 Env env{*this, testableAmendments()};
526 Account const alice("alice");
527 Account const sponsor("sponsor");
528 env.fund(XRP(10000), alice, sponsor);
529 env.close();
530
531 {
532 // create sponsorship
533 env(sponsor::set(
534 sponsor,
535 tfSponsorshipSetRequireSignForFee | tfSponsorshipSetRequireSignForReserve,
536 100,
537 XRP(100),
538 XRP(1)),
539 Fee(XRP(1)),
540 sponsor::SponseeAcc(alice),
541 Ter(tesSUCCESS));
542 env.close();
543
544 auto sle = env.le(keylet::sponsorship(sponsor, alice));
545 BEAST_EXPECT(sle);
546 BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 100);
547 BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(100));
548 BEAST_EXPECT(sle->at(sfMaxFee) == XRP(1));
549 BEAST_EXPECT(sle->isFlag(lsfSponsorshipRequireSignForFee));
550 BEAST_EXPECT(sle->isFlag(lsfSponsorshipRequireSignForReserve));
551 BEAST_EXPECT(env.balance(sponsor) == XRP(10000) - sle->at(sfFeeAmount) - XRP(1));
552
553 // update sponsorship (decrement)
554 env(sponsor::set(sponsor, 0, -50, XRP(-50), XRP(0.5)),
555 sponsor::SponseeAcc(alice),
556 Fee(XRP(1)),
557 Ter(tesSUCCESS));
558 env.close();
559
560 sle = env.le(keylet::sponsorship(sponsor, alice));
561 BEAST_EXPECT(sle);
562 BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 50);
563 BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(50));
564 BEAST_EXPECT(sle->at(sfMaxFee) == XRP(0.5));
565 BEAST_EXPECT(env.balance(sponsor) == XRP(10000) - sle->at(sfFeeAmount) - XRP(2));
566
567 // update sponsorship (increment)
568 env(sponsor::set(sponsor, 0, 150, XRP(150), XRP(2)),
569 sponsor::SponseeAcc(alice),
570 Fee(XRP(1)),
571 Ter(tesSUCCESS));
572 env.close();
573
574 sle = env.le(keylet::sponsorship(sponsor, alice));
575 BEAST_EXPECT(sle);
576 BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 200);
577 BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(200));
578 BEAST_EXPECT(sle->at(sfMaxFee) == XRP(2));
579 BEAST_EXPECT(env.balance(sponsor) == XRP(10000) - sle->at(sfFeeAmount) - XRP(3));
580
581 // delete from sponsor
583 env.close();
584
585 BEAST_EXPECT(env.balance(sponsor) == XRP(10000) - XRP(4));
586
587 env(sponsor::set(
588 sponsor,
589 tfSponsorshipSetRequireSignForFee | tfSponsorshipSetRequireSignForReserve,
590 100,
591 XRP(100),
592 XRP(1)),
593 sponsor::SponseeAcc(alice),
594 Ter(tesSUCCESS));
595 env.close();
596
597 // delete from sponsee
599 env.close();
600 BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice)));
601
602 env(sponsor::set_max_fee(sponsor, 0, XRP(1)),
603 sponsor::SponseeAcc(alice),
605 env.close();
606 BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice)));
607
608 env(sponsor::set(sponsor, 0, std::nullopt, std::nullopt, XRP(0)),
609 sponsor::SponseeAcc(alice),
611 env.close();
612 BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice)));
613
614 // create sponsorship with negative values
615 env(sponsor::set_reserve(sponsor, 0, -100),
616 sponsor::SponseeAcc(alice),
618 env.close();
619 BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice)));
620 env(sponsor::set_fee(sponsor, 0, XRP(-100)),
621 sponsor::SponseeAcc(alice),
623 env.close();
624 BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice)));
625
626 // create sponsorship with non-zero value
627 env(sponsor::set(sponsor, 0, 100, XRP(101), XRP(1)),
628 sponsor::SponseeAcc(alice),
629 Fee(XRP(1)));
630 env.close();
631
632 sle = env.le(keylet::sponsorship(sponsor, alice));
633 BEAST_EXPECT(sle);
634 BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 100);
635 BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(101));
636 BEAST_EXPECT(sle->at(sfMaxFee) == XRP(1));
637
638 // update sponsorship flags
639 auto testFlagUpdate = [&](auto setFlag, auto clearFlag, auto ledgerFlag) {
640 env(sponsor::set(sponsor, setFlag), sponsor::SponseeAcc(alice), Fee(XRP(1)));
641 env.close();
642
643 sle = env.le(keylet::sponsorship(sponsor, alice));
644 BEAST_EXPECT(sle);
645 BEAST_EXPECT(sle->isFlag(ledgerFlag));
646
647 env(sponsor::set(sponsor, clearFlag), sponsor::SponseeAcc(alice), Fee(XRP(1)));
648 env.close();
649
650 sle = env.le(keylet::sponsorship(sponsor, alice));
651 BEAST_EXPECT(sle);
652 BEAST_EXPECT(!sle->isFlag(ledgerFlag));
653 };
654
655 testFlagUpdate(
656 tfSponsorshipSetRequireSignForFee,
657 tfSponsorshipClearRequireSignForFee,
658 lsfSponsorshipRequireSignForFee);
659 testFlagUpdate(
660 tfSponsorshipSetRequireSignForReserve,
661 tfSponsorshipClearRequireSignForReserve,
662 lsfSponsorshipRequireSignForReserve);
663
664 // Cannot update sponsorship so both fee and reserve budgets are absent.
665 env(sponsor::set(sponsor, 0, -100, XRP(-101), std::nullopt),
666 sponsor::SponseeAcc(alice),
667 Fee(XRP(1)),
669 env.close();
670
671 sle = env.le(keylet::sponsorship(sponsor, alice));
672 BEAST_EXPECT(sle);
673 BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 100);
674 BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(101));
675 BEAST_EXPECT(sle->at(sfMaxFee) == XRP(1));
676 }
677
678 {
679 // Removing one budget field while the other remains keeps the
680 // Sponsorship valid. Starting state (from above):
681 // RemainingOwnerCount = 100, FeeAmount = XRP(101).
682
683 // Remove only FeeAmount (set to 0); RemainingOwnerCount remains.
684 env(sponsor::set_fee(sponsor, 0, XRP(-101)),
685 sponsor::SponseeAcc(alice),
686 Fee(XRP(1)),
687 Ter(tesSUCCESS));
688 env.close();
689
690 auto sle = env.le(keylet::sponsorship(sponsor, alice));
691 BEAST_EXPECT(sle);
692 BEAST_EXPECT(!sle->isFieldPresent(sfFeeAmount));
693 BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 100);
694
695 // Re-add FeeAmount, then remove only RemainingOwnerCount;
696 // FeeAmount remains.
697 env(sponsor::set_fee(sponsor, 0, XRP(100)),
698 sponsor::SponseeAcc(alice),
699 Fee(XRP(1)),
700 Ter(tesSUCCESS));
701 env.close();
702
703 // A negative FeeAmountDelta larger than the current FeeAmount is
704 // clamped, so only the current FeeAmount is refunded and the field
705 // is removed. RemainingOwnerCount keeps the Sponsorship valid.
706 auto const balanceBefore = env.balance(sponsor);
707 env(sponsor::set_fee(sponsor, 0, XRP(-500)),
708 sponsor::SponseeAcc(alice),
709 Fee(XRP(1)),
710 Ter(tesSUCCESS));
711 env.close();
712
713 sle = env.le(keylet::sponsorship(sponsor, alice));
714 BEAST_EXPECT(sle);
715 BEAST_EXPECT(!sle->isFieldPresent(sfFeeAmount));
716 BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 100);
717 BEAST_EXPECT(env.balance(sponsor) == balanceBefore + XRP(100) - XRP(1));
718
719 // Restore FeeAmount for the checks below.
720 env(sponsor::set_fee(sponsor, 0, XRP(100)),
721 sponsor::SponseeAcc(alice),
722 Fee(XRP(1)),
723 Ter(tesSUCCESS));
724 env.close();
725
726 env(sponsor::set_reserve(sponsor, 0, -100),
727 sponsor::SponseeAcc(alice),
728 Fee(XRP(1)),
729 Ter(tesSUCCESS));
730 env.close();
731
732 sle = env.le(keylet::sponsorship(sponsor, alice));
733 BEAST_EXPECT(sle);
734 BEAST_EXPECT(!sle->isFieldPresent(sfRemainingOwnerCount));
735 BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(100));
736
737 // Decreasing FeeAmount below zero must fail with tecNO_PERMISSION
738 // when there is no RemainingOwnerCount (the budget would become
739 // entirely empty). Current state: FeeAmount = XRP(100), no
740 // RemainingOwnerCount.
741 env(sponsor::set_fee(sponsor, 0, XRP(-101)),
742 sponsor::SponseeAcc(alice),
743 Fee(XRP(1)),
745 env.close();
746
747 // Confirm that the sponsorship is unchanged.
748 sle = env.le(keylet::sponsorship(sponsor, alice));
749 BEAST_EXPECT(sle);
750 BEAST_EXPECT(!sle->isFieldPresent(sfRemainingOwnerCount));
751 BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(100));
752 }
753
754 {
755 // Update Sponsorship (FeeAmount)
756 // set empty FeeAmount
758 env.close();
759
760 // add FeeAmount
761 env(sponsor::set_fee(sponsor, 0, XRP(100)),
762 sponsor::SponseeAcc(alice),
763 Ter(tesSUCCESS));
764 env.close();
765
767 env.close();
768 }
769 {
770 // Update Sponsorship (ReserveCount)
771 // set empty ReserveCount
772 env(sponsor::set_fee(sponsor, 0, XRP(100)),
773 sponsor::SponseeAcc(alice),
774 Ter(tesSUCCESS));
775 env.close();
776
777 // add ReserveCount
779 env.close();
780
782 env.close();
783 }
784 {
785 // delete Sponsorship (only with FeeAmount)
786 env(sponsor::set_fee(sponsor, 0, XRP(100)),
787 sponsor::SponseeAcc(alice),
788 Ter(tesSUCCESS));
789 env.close();
790
792 env.close();
793 }
794 {
795 // delete Sponsorship (only with ReserveCount)
797 env.close();
798
800 env.close();
801 }
802 }
803
804 void
806 {
807 testcase("RemainingOwnerCount overflow and underflow clamping");
808 using namespace test::jtx;
809 Env env{*this, testableAmendments()};
810 Account const alice("alice");
811 Account const sponsor("sponsor");
812 env.fund(XRP(10000), alice, sponsor);
813 env.close();
814
816
817 // --- Positive overflow: delta causes count to exceed UINT32_MAX ---
818 {
819 // Create with count = INT32_MAX.
820 env(sponsor::set_reserve(sponsor, 0, kInt32Max),
821 sponsor::SponseeAcc(alice),
822 Ter(tesSUCCESS));
823 env.close();
824 BEAST_EXPECT(
825 env.le(keylet::sponsorship(sponsor, alice))->at(sfRemainingOwnerCount) ==
826 static_cast<std::uint32_t>(kInt32Max));
827
828 // Add INT32_MAX again: count = 2 * INT32_MAX = 4294967294 (<= UINT32_MAX, still ok).
829 env(sponsor::set_reserve(sponsor, 0, kInt32Max),
830 sponsor::SponseeAcc(alice),
831 Ter(tesSUCCESS));
832 env.close();
833 BEAST_EXPECT(
834 env.le(keylet::sponsorship(sponsor, alice))->at(sfRemainingOwnerCount) ==
835 2u * static_cast<std::uint32_t>(kInt32Max));
836
837 // Adding 2 more pushes count to 4294967296, exceeding UINT32_MAX: reject.
838 env(sponsor::set_reserve(sponsor, 0, 2),
839 sponsor::SponseeAcc(alice),
841 env.close();
842
843 // SLE is unchanged.
844 BEAST_EXPECT(
845 env.le(keylet::sponsorship(sponsor, alice))->at(sfRemainingOwnerCount) ==
846 2u * static_cast<std::uint32_t>(kInt32Max));
847
849 env.close();
850 }
851
852 // --- Negative underflow: clamps to 0; fee budget survives ---
853 {
854 // Create with count=10 and a fee budget.
855 env(sponsor::set(sponsor, 0, 10, XRP(100)),
856 sponsor::SponseeAcc(alice),
857 Ter(tesSUCCESS));
858 env.close();
859 BEAST_EXPECT(
860 env.le(keylet::sponsorship(sponsor, alice))->at(sfRemainingOwnerCount) == 10u);
861
862 // Delta of -20 produces count = -10; clamps to 0 (field absent).
864 env.close();
865
866 auto sle = env.le(keylet::sponsorship(sponsor, alice));
867 BEAST_EXPECT(sle);
868 BEAST_EXPECT(!sle->isFieldPresent(sfRemainingOwnerCount));
869 BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(100));
870
872 env.close();
873 }
874
875 // --- Negative underflow: clamped count=0 with no fee budget → no budget ---
876 {
877 // Create with count=10, no fee.
879 env.close();
880 BEAST_EXPECT(
881 env.le(keylet::sponsorship(sponsor, alice))->at(sfRemainingOwnerCount) == 10u);
882
883 // Delta of -20 would clamp count to 0 with no fee → empty budget → tecNO_PERMISSION.
884 env(sponsor::set_reserve(sponsor, 0, -20),
885 sponsor::SponseeAcc(alice),
887 env.close();
888
889 // SLE is unchanged.
890 BEAST_EXPECT(
891 env.le(keylet::sponsorship(sponsor, alice))->at(sfRemainingOwnerCount) == 10u);
892
894 env.close();
895 }
896 }
897
898 void
900 {
901 testcase("Consequences");
902 using namespace test::jtx;
903 Env env{*this, testableAmendments()};
904 auto const baseFee = env.current()->fees().base;
905
906 Account const alice("alice");
907 Account const sponsor("sponsor");
908 env.memoize(alice);
909 env.memoize(sponsor);
910
911 {
912 // A positive FeeAmountDelta is the maximum XRP the tx can spend.
913 auto const jt = env.jt(
914 sponsor::set_fee(sponsor, 0, XRP(100)),
915 sponsor::SponseeAcc(alice),
916 Seq(1),
917 Fee(baseFee));
918 auto const pf =
919 preflight(env.app(), env.current()->rules(), *jt.stx, TapNone, env.journal);
920 BEAST_EXPECT(isTesSuccess(pf.ter));
921 BEAST_EXPECT(!pf.consequences.isBlocker());
922 BEAST_EXPECT(pf.consequences.fee() == drops(baseFee));
923 BEAST_EXPECT(pf.consequences.potentialSpend() == XRP(100));
924 }
925
926 {
927 // A negative FeeAmountDelta withdraws from the sponsorship, so the
928 // transaction cannot spend anything.
929 auto const jt = env.jt(
930 sponsor::set_fee(sponsor, 0, XRP(-100)),
931 sponsor::SponseeAcc(alice),
932 Seq(1),
933 Fee(baseFee));
934 auto const pf =
935 preflight(env.app(), env.current()->rules(), *jt.stx, TapNone, env.journal);
936 BEAST_EXPECT(isTesSuccess(pf.ter));
937 BEAST_EXPECT(!pf.consequences.isBlocker());
938 BEAST_EXPECT(pf.consequences.fee() == drops(baseFee));
939 BEAST_EXPECT(pf.consequences.potentialSpend() == XRP(0));
940 }
941
942 {
943 // No FeeAmountDelta at all.
944 auto const jt = env.jt(
946 sponsor::SponseeAcc(alice),
947 Seq(1),
948 Fee(baseFee));
949 auto const pf =
950 preflight(env.app(), env.current()->rules(), *jt.stx, TapNone, env.journal);
951 BEAST_EXPECT(isTesSuccess(pf.ter));
952 BEAST_EXPECT(!pf.consequences.isBlocker());
953 BEAST_EXPECT(pf.consequences.fee() == drops(baseFee));
954 BEAST_EXPECT(pf.consequences.potentialSpend() == XRP(0));
955 }
956 }
957
958 void
960 {
961 testcase("PreFund and Cosign");
962 using namespace test::jtx;
963 Account const alice("alice");
964 Account const bob("bob");
965 Account const charlie("charlie");
966 Account const sponsor("sponsor");
967
968 {
969 // Both pre-funded and co-signed; the pre-funded value is used.
970 Env env{*this, testableAmendments()};
971 env.fund(XRP(10000), alice, bob, sponsor);
972 env.close();
973
974 env(sponsor::set(sponsor, 0, 100, XRP(100), XRP(1)),
975 sponsor::SponseeAcc(alice),
976 Ter(tesSUCCESS));
977 env.close();
978
979 auto const checkSeq = env.seq(alice);
980 env(check::create(alice, bob, XRP(1)),
982 Sig(sfSponsorSignature, sponsor),
983 Fee(XRP(1)),
984 Ter(tesSUCCESS));
985 env.close();
986
987 auto sle = env.le(keylet::sponsorship(sponsor, alice));
988 BEAST_EXPECT(sle);
989 BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 99);
990 BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(99));
991
992 env(check::cancel(alice, keylet::check(alice, SeqProxy::rawSequence(checkSeq)).key),
993 Ter(tesSUCCESS));
994 env.close();
995
996 sle = env.le(keylet::sponsorship(sponsor, alice));
997 BEAST_EXPECT(sle);
998 BEAST_EXPECT(sle->at(sfRemainingOwnerCount) == 99); // not restored
999 BEAST_EXPECT(sle->at(sfFeeAmount) == XRP(99));
1000 }
1001
1002 {
1003 // if pre-funded value is not enough, error
1004 Env env{*this, testableAmendments()};
1005 env.fund(XRP(10000), alice, bob, charlie, sponsor);
1006 env.close();
1007
1008 env(sponsor::set(sponsor, 0, 1, XRP(10), XRP(100)),
1009 sponsor::SponseeAcc(alice),
1010 Ter(tesSUCCESS));
1011 env.close();
1012
1013 // Fee insufficient
1014 env(check::create(alice, bob, XRP(1)),
1016 Sig(sfSponsorSignature, sponsor),
1017 Fee(XRP(11)),
1019 env.close();
1020
1022 env.close();
1023
1024 // reserve insufficient
1025 env(check::create(alice, bob, XRP(1)),
1027 Sig(sfSponsorSignature, sponsor),
1028 Fee(XRP(1)),
1030 env.close();
1031 }
1032 }
1033
1034 void
1036 {
1037 testcase("Sponsored Free-Tier Reserve");
1038 using namespace test::jtx;
1039 Account const alice("alice");
1040 Account const issuer("issuer");
1041 Account const sponsor("sponsor");
1042
1043 // Trust lines and MPTokens normally skip the reserve check when the
1044 // holder's ownerCount < 2 (the "free-tier" / first-two-items shortcut). When the
1045 // tx is sponsored, that shortcut must not apply — the sponsor must
1046 // still cover the reserve.
1047 Env env{*this, testableAmendments()};
1048 env.fund(XRP(10000), alice, issuer);
1049 // Sponsor is funded just below the reserve required to cover a single
1050 // sponsored item.
1051 env.fund(reserve(env, 1) - drops(1), sponsor);
1052 env.close();
1053 BEAST_EXPECT(ownerCount(env, alice) == 0);
1054
1055 MPTTester mptt(env, issuer, {.fund = false});
1056 mptt.create();
1057
1058 // Free-tier trust line cosigned by an undercapitalized sponsor must
1059 // fail — the holder's free-first-two-items shortcut does not let the
1060 // sponsor skip the reserve check.
1061 env(trust(alice, issuer["USD"](100)),
1063 Sig(sfSponsorSignature, sponsor),
1065 env.close();
1066
1067 // Free-tier MPTokenAuthorize must also fail for the same reason.
1068 env(MPTTester::authorizeJV({.account = alice, .id = mptt.issuanceID()}),
1070 Sig(sfSponsorSignature, sponsor),
1072 env.close();
1073 }
1074
1075 void
1077 {
1078 testcase("Transfer Sponsor");
1079 using namespace test::jtx;
1080
1081 // Verify preflight checks
1082 {
1083 Env env{*this, testableAmendments()};
1084 Account const alice("alice");
1085 Account const bob("bob");
1086 Account const sponsor("sponsor");
1087 env.fund(XRP(10000), alice, bob, sponsor);
1088 env.close();
1089
1090 // Test invalid flags for SponsorshipTransfer
1091 {
1092 // Invalid flag is provided
1094 alice,
1095 (tfSponsorshipCreate | tfSponsorshipReassign | tfSponsorshipEnd) + 1),
1097
1098 // No SponsorshipTransfer flag is provided
1099 env(sponsor::transfer(alice, 0), Ter(temINVALID_FLAG));
1100
1101 // Only one of the three valid flags can be set.
1102 // Setting more than one flag is invalid
1103 for (auto flag : {
1104 tfSponsorshipCreate | tfSponsorshipReassign,
1105 tfSponsorshipCreate | tfSponsorshipEnd,
1106 tfSponsorshipReassign | tfSponsorshipEnd,
1107 tfSponsorshipCreate | tfSponsorshipReassign | tfSponsorshipEnd,
1108 })
1109 env(sponsor::transfer(alice, flag), Ter(temINVALID_FLAG));
1110 }
1111
1112 // Malformed tests for tfSponsorshipCreate
1113 {
1114 // No sponsor field present
1115 env(sponsor::transfer(alice, tfSponsorshipCreate), Ter(temMALFORMED));
1116
1117 // Sponsor field present without spfSponsorReserve
1118 env(sponsor::transfer(alice, tfSponsorshipCreate),
1121
1122 // Sponsee field present
1123 env(sponsor::transfer(alice, tfSponsorshipCreate),
1126 Ter(temMALFORMED));
1127 }
1128
1129 // Malformed tests for tfSponsorshipReassign
1130 {
1131 // No sponsor field present
1132 env(sponsor::transfer(alice, tfSponsorshipReassign), Ter(temMALFORMED));
1133
1134 // Sponsor field present without spfSponsorReserve
1135 env(sponsor::transfer(alice, tfSponsorshipReassign),
1138
1139 // Sponsee field present
1140 env(sponsor::transfer(alice, tfSponsorshipReassign),
1143 Ter(temMALFORMED));
1144 }
1145
1146 // Malformed tests for tfSponsorshipEnd
1147 {
1148 // Sponsor field present
1149 env(sponsor::transfer(alice, tfSponsorshipEnd),
1151 Ter(temMALFORMED));
1152
1153 // SponsorFlags field present
1154 auto tx = sponsor::transfer(alice, tfSponsorshipEnd);
1155 tx[sfSponsorFlags.jsonName] = spfSponsorFee;
1156 env(tx, Ter(temINVALID_FLAG));
1157
1158 // Account = Sponsee
1159 env(sponsor::transfer(alice, tfSponsorshipEnd),
1160 sponsor::SponseeAcc(alice),
1161 Ter(temMALFORMED));
1162 }
1163 }
1164
1165 {
1166 // Invalid SponsorshipEnd permission (sponsor object/sponsor account)
1167 Env env{*this, testableAmendments()};
1168 Account const alice("alice");
1169 Account const bob("bob");
1170 Account const charlie("charlie");
1171 Account const sponsor("sponsor");
1172 env.fund(XRP(10000), alice, bob, sponsor);
1173 env.close();
1174
1175 {
1176 // sponsor object
1177 env.fund(XRP(1000), charlie);
1178 env.close();
1179 env(deposit::auth(alice, charlie),
1181 Sig(sfSponsorSignature, sponsor));
1182 env.close();
1183
1184 auto const keylet = keylet::depositPreauth(alice, charlie);
1185 env(sponsor::transfer(bob, tfSponsorshipEnd, keylet.key),
1186 sponsor::SponseeAcc(alice),
1188 }
1189 {
1190 // sponsor object
1191 env(sponsor::transfer(alice, tfSponsorshipCreate),
1193 Sig(sfSponsorSignature, sponsor));
1194 env.close();
1195
1196 env(sponsor::transfer(bob, tfSponsorshipEnd),
1197 sponsor::SponseeAcc(alice),
1199 }
1200 {
1201 // The provided sfSponsee account does not exist
1202 // when ending sponsorship.
1203 Account const ghost("ghost"); // never funded, absent from ledger
1204 env(sponsor::transfer(sponsor, tfSponsorshipEnd),
1205 sponsor::SponseeAcc(ghost),
1207 }
1208 }
1209
1210 {
1211 // sponsor account
1212 Env env{*this, testableAmendments()};
1213 Account const alice("alice");
1214 Account const bob("bob");
1215 Account const sponsor1("sponsor1");
1216 Account const sponsor2("sponsor2");
1217 env.fund(XRP(10000), alice, bob, sponsor1, sponsor2);
1218
1219 // sfSponsor provided but sfSponsorSignature not provided
1220 env(sponsor::transfer(alice, tfSponsorshipCreate),
1221 sponsor::As(sponsor1, spfSponsorReserve),
1222 Ter(temMALFORMED));
1223 env.close();
1224
1225 adjustAccountXRPBalance(env, sponsor1, accountReserve(env, 2) - drops(1));
1226
1227 env(sponsor::transfer(alice, tfSponsorshipCreate),
1228 sponsor::As(sponsor1, spfSponsorReserve),
1229 Sig(sfSponsorSignature, sponsor1),
1231 env.close();
1232
1233 adjustAccountXRPBalance(env, sponsor1, accountReserve(env, 2));
1234
1235 env(sponsor::transfer(alice, tfSponsorshipCreate),
1236 sponsor::As(sponsor1, spfSponsorReserve),
1237 Sig(sfSponsorSignature, sponsor1));
1238 env.close();
1239
1240 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
1241 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
1242 BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
1243 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
1244 BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
1245 BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 1);
1246 auto const sle1 = env.le(keylet::account(alice));
1247 BEAST_EXPECT(sle1->isFieldPresent(sfSponsor));
1248 BEAST_EXPECT(sle1->getAccountID(sfSponsor) == sponsor1.id());
1249
1250 // transfer sponsor
1251 adjustAccountXRPBalance(env, sponsor2, accountReserve(env, 2) - drops(1));
1252
1253 env(sponsor::transfer(alice, tfSponsorshipReassign),
1254 sponsor::As(sponsor2, spfSponsorReserve),
1255 Sig(sfSponsorSignature, sponsor2),
1257 env.close();
1258
1259 adjustAccountXRPBalance(env, sponsor2, accountReserve(env, 2));
1260
1261 env(sponsor::transfer(alice, tfSponsorshipReassign),
1262 sponsor::As(sponsor2, spfSponsorReserve),
1263 Sig(sfSponsorSignature, sponsor2));
1264 env.close();
1265
1266 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
1267 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
1268 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0);
1269 BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
1270 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
1271 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
1272 BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
1273 BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
1274 BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 1);
1275 BEAST_EXPECT(
1276 !env.le(keylet::account(sponsor1))->isFieldPresent(sfSponsoringAccountCount));
1277 auto const sle2 = env.le(keylet::account(alice));
1278 BEAST_EXPECT(sle2->isFieldPresent(sfSponsor));
1279 BEAST_EXPECT(sle2->getAccountID(sfSponsor) == sponsor2.id());
1280
1281 // Reassign to the current sponsor is a no-op and is rejected
1282 env(sponsor::transfer(alice, tfSponsorshipReassign),
1283 sponsor::As(sponsor2, spfSponsorReserve),
1284 Sig(sfSponsorSignature, sponsor2),
1286 env.close();
1287
1288 BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 1);
1289
1290 // sponsor 2 accounts
1291 adjustAccountXRPBalance(env, sponsor2, accountReserve(env, 3));
1292 env(sponsor::transfer(bob, tfSponsorshipCreate),
1293 sponsor::As(sponsor2, spfSponsorReserve),
1294 Sig(sfSponsorSignature, sponsor2));
1295 env.close();
1296
1297 // dissolve sponsors
1298 adjustAccountXRPBalance(env, alice, accountReserve(env, 1) - drops(1));
1299
1300 env(sponsor::transfer(alice, tfSponsorshipEnd), Ter(tecINSUFFICIENT_RESERVE));
1301 env.close();
1302
1303 adjustAccountXRPBalance(env, alice, accountReserve(env, 1));
1304
1305 env(sponsor::transfer(alice, tfSponsorshipEnd));
1306 env.close();
1307
1308 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
1309 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
1310 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0);
1311 BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
1312 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
1313 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
1314 BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
1315 BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
1316 BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 1);
1317 auto const sle3 = env.le(keylet::account(alice));
1318 BEAST_EXPECT(!sle3->isFieldPresent(sfSponsor));
1319
1320 env(sponsor::transfer(bob, tfSponsorshipEnd));
1321 env.close();
1322
1323 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
1324 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
1325 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0);
1326 BEAST_EXPECT(sponsoringOwnerCount(env, bob) == 0);
1327 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
1328 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
1329 BEAST_EXPECT(sponsoringAccountCount(env, bob) == 0);
1330 BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
1331 BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0);
1332 BEAST_EXPECT(
1333 !env.le(keylet::account(sponsor2))->isFieldPresent(sfSponsoringAccountCount));
1334 auto const sle4 = env.le(keylet::account(bob));
1335 BEAST_EXPECT(!sle4->isFieldPresent(sfSponsor));
1336
1337 // not sponsored
1338 env(sponsor::transfer(bob, tfSponsorshipEnd), Ter(tecNO_PERMISSION));
1339 env.close();
1340 }
1341 {
1342 // dissolve account sponsorship from sponsor
1343 Env env{*this, testableAmendments()};
1344 Account const alice("alice");
1345 Account const bob("bob");
1346 Account const sponsor("sponsor");
1347 env.fund(XRP(10000), alice, bob, sponsor);
1348 env.close();
1349
1350 env(sponsor::transfer(alice, tfSponsorshipCreate),
1352 Sig(sfSponsorSignature, sponsor));
1353 env.close();
1354
1355 BEAST_EXPECT(env.le(alice)->getAccountID(sfSponsor) == sponsor.id());
1356 BEAST_EXPECT(sponsoringAccountCount(env, sponsor) == 1);
1357
1358 env(sponsor::transfer(sponsor, tfSponsorshipEnd), sponsor::SponseeAcc(alice));
1359 env.close();
1360
1361 BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfSponsor));
1362 BEAST_EXPECT(sponsoringAccountCount(env, sponsor) == 0);
1363 }
1364
1365 {
1366 // sponsor object (co-signing)
1367 Env env{*this, testableAmendments()};
1368 Account const alice("alice");
1369 Account const bob("bob");
1370 Account const sponsor1("sponsor1");
1371 Account const sponsor2("sponsor2");
1372 env.fund(XRP(10000), alice, bob, sponsor1, sponsor2);
1373 env.close();
1374
1375 adjustAccountXRPBalance(env, sponsor1, reserve(env, 1) - drops(1));
1376 adjustAccountXRPBalance(env, sponsor2, reserve(env, 1) - drops(1));
1377
1378 auto const seq = env.seq(alice);
1379 env(check::create(alice, bob, XRP(1)));
1380 env.close();
1381
1382 auto const checkId = keylet::check(alice, SeqProxy::rawSequence(seq)).key;
1383 BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr);
1384
1385 env(sponsor::transfer(alice, tfSponsorshipCreate, checkId),
1386 sponsor::As(sponsor1, spfSponsorReserve),
1387 Sig(sfSponsorSignature, sponsor1),
1389 env.close();
1390
1391 env(pay(alice, sponsor1, drops(1)));
1392 env.close();
1393
1394 // Invalid ObjectID (not found)
1396 alice, tfSponsorshipCreate, keylet::check(alice, SeqProxy::rawSequence(0)).key),
1397 sponsor::As(sponsor1, spfSponsorReserve),
1398 Sig(sfSponsorSignature, sponsor1),
1399 Ter(tecNO_ENTRY));
1400 env.close();
1401
1402 // Invalid Owner
1403 env(sponsor::transfer(bob, tfSponsorshipCreate, checkId),
1404 sponsor::As(sponsor1, spfSponsorReserve),
1405 Sig(sfSponsorSignature, sponsor1),
1407 env.close();
1408
1409 // Reassign an object that is not sponsored yet
1410 env(sponsor::transfer(alice, tfSponsorshipReassign, checkId),
1411 sponsor::As(sponsor1, spfSponsorReserve),
1412 Sig(sfSponsorSignature, sponsor1),
1414 env.close();
1415
1416 // Valid Owner
1417 env(sponsor::transfer(alice, tfSponsorshipCreate, checkId),
1418 sponsor::As(sponsor1, spfSponsorReserve),
1419 Sig(sfSponsorSignature, sponsor1));
1420 env.close();
1421
1422 BEAST_EXPECT(ownerCount(env, alice) == 1);
1423 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
1424 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
1425 BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
1426 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 1);
1427 BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
1428 BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
1429 auto const sle1 = env.le(keylet::unchecked(checkId));
1430 BEAST_EXPECT(sle1->isFieldPresent(sfSponsor));
1431 BEAST_EXPECT(sle1->getAccountID(sfSponsor) == sponsor1.id());
1432
1433 // Create on an object that is already sponsored
1434 env(sponsor::transfer(alice, tfSponsorshipCreate, checkId),
1435 sponsor::As(sponsor2, spfSponsorReserve),
1436 Sig(sfSponsorSignature, sponsor2),
1438 env.close();
1439
1440 // Reassign to the current sponsor is a no-op and is rejected
1441 env(sponsor::transfer(alice, tfSponsorshipReassign, checkId),
1442 sponsor::As(sponsor1, spfSponsorReserve),
1443 Sig(sfSponsorSignature, sponsor1),
1445 env.close();
1446
1447 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 1);
1448
1449 // transfer sponsor
1450 env(sponsor::transfer(alice, tfSponsorshipReassign, checkId),
1451 sponsor::As(sponsor2, spfSponsorReserve),
1452 Sig(sfSponsorSignature, sponsor2),
1454
1455 env(pay(alice, sponsor2, drops(1)));
1456 env.close();
1457
1458 env(sponsor::transfer(alice, tfSponsorshipReassign, checkId),
1459 sponsor::As(sponsor2, spfSponsorReserve),
1460 Sig(sfSponsorSignature, sponsor2));
1461 env.close();
1462
1463 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
1464 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
1465 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0);
1466 BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
1467 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
1468 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
1469 BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
1470 BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
1471 BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0);
1472 auto const sle2 = env.le(keylet::unchecked(checkId));
1473 BEAST_EXPECT(sle2->isFieldPresent(sfSponsor));
1474 BEAST_EXPECT(sle2->getAccountID(sfSponsor) == sponsor2.id());
1475
1476 // dissolve sponsor: ending an object sponsorship succeeds even
1477 // when the sponsee lacks sufficient reserve to reclaim the object.
1478 adjustAccountXRPBalance(env, alice, reserve(env, 1) - drops(1));
1479
1480 env(sponsor::transfer(alice, tfSponsorshipEnd, checkId));
1481 env.close();
1482
1483 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
1484 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
1485 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0);
1486 BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
1487 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
1488 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
1489 BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
1490 BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
1491 BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0);
1492 BEAST_EXPECT(
1493 !env.le(keylet::account(sponsor2))->isFieldPresent(sfSponsoringOwnerCount));
1494 auto const sle3 = env.le(keylet::unchecked(checkId));
1495 BEAST_EXPECT(!sle3->isFieldPresent(sfSponsor));
1496
1497 // Ending sponsorship on an object that is not sponsored (a ticket,
1498 // which cannot be sponsored) is rejected.
1499 adjustAccountXRPBalance(env, alice, reserve(env, 2));
1500 auto const ticketSeq = env.seq(alice);
1501 env(ticket::create(alice, 1));
1502 env.close();
1503 auto ticketId = keylet::ticket(alice, SeqProxy::rawTicket(ticketSeq + 1)).key;
1504 BEAST_EXPECT(env.le(keylet::unchecked(ticketId)));
1505 env(sponsor::transfer(alice, tfSponsorshipEnd, ticketId), Ter(tecNO_PERMISSION));
1506 env.close();
1507 env(noop(alice), ticket::Use(ticketSeq + 1));
1508 env.close();
1509 }
1510 {
1511 // sponsor object (pre-funded + no ltSponsorship entry)
1512 Env env{*this, testableAmendments()};
1513 Account const alice("alice");
1514 Account const bob("bob");
1515 Account const sponsor1("sponsor1");
1516 Account const sponsor2("sponsor2");
1517 env.fund(XRP(10000), alice, bob, sponsor1, sponsor2);
1518 env.close();
1519
1520 auto const seq = env.seq(alice);
1521 env(check::create(alice, bob, XRP(1)));
1522 env.close();
1523
1524 auto const checkId = keylet::check(alice, SeqProxy::rawSequence(seq)).key;
1525 BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr);
1526
1527 env(sponsor::transfer(alice, tfSponsorshipCreate, checkId),
1528 sponsor::As(sponsor1, spfSponsorReserve),
1530 env.close();
1531
1532 env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice));
1533 env.close();
1534
1535 env(sponsor::transfer(alice, tfSponsorshipCreate, checkId),
1536 sponsor::As(sponsor2, spfSponsorReserve));
1537 env.close();
1538
1539 env(sponsor::transfer(alice, tfSponsorshipReassign, checkId),
1540 sponsor::As(sponsor1, spfSponsorReserve),
1542 env.close();
1543 }
1544 {
1545 // sponsor object (pre-funded)
1546 Env env{*this, testableAmendments()};
1547 Account const alice("alice");
1548 Account const bob("bob");
1549 Account const sponsor1("sponsor1");
1550 Account const sponsor2("sponsor2");
1551 env.fund(XRP(10000), alice, bob, sponsor1, sponsor2);
1552 env.close();
1553
1554 auto const seq = env.seq(alice);
1555 env(check::create(alice, bob, XRP(1)));
1556 env.close();
1557
1558 auto const checkId = keylet::check(alice, SeqProxy::rawSequence(seq)).key;
1559 BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr);
1560
1561 // insufficient reserve count
1562 env(sponsor::set_fee(sponsor1, 0, XRP(100)), sponsor::SponseeAcc(alice));
1563 env.close();
1564 env(sponsor::transfer(alice, tfSponsorshipCreate, checkId),
1565 sponsor::As(sponsor1, spfSponsorReserve),
1567 env.close();
1568
1569 env(sponsor::set_reserve(sponsor1, 0, 100), sponsor::SponseeAcc(alice));
1570 env.close();
1571
1572 env(sponsor::transfer(alice, tfSponsorshipCreate, checkId),
1573 sponsor::As(sponsor1, spfSponsorReserve));
1574 env.close();
1575
1576 BEAST_EXPECT(ownerCount(env, alice) == 1);
1577 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
1578 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
1579 BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
1580 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 1);
1581 BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
1582 BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
1583 auto checkSle = env.le(keylet::unchecked(checkId));
1584 BEAST_EXPECT(checkSle->isFieldPresent(sfSponsor));
1585 BEAST_EXPECT(checkSle->getAccountID(sfSponsor) == sponsor1.id());
1586 auto sponsor1Sle = env.le(keylet::sponsorship(sponsor1, alice));
1587 BEAST_EXPECT(sponsor1Sle->getFieldU32(sfRemainingOwnerCount) == 99);
1588
1589 // Reassign to the current sponsor is rejected and must not draw
1590 // down the pre-funded reserve budget
1591 env(sponsor::transfer(alice, tfSponsorshipReassign, checkId),
1592 sponsor::As(sponsor1, spfSponsorReserve),
1594 env.close();
1595
1596 sponsor1Sle = env.le(keylet::sponsorship(sponsor1, alice));
1597 BEAST_EXPECT(sponsor1Sle->getFieldU32(sfRemainingOwnerCount) == 99);
1598 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 1);
1599
1600 // transfer sponsor
1601 env(sponsor::set_reserve(sponsor2, 0, 100), sponsor::SponseeAcc(alice));
1602 env.close();
1603
1604 env(sponsor::transfer(alice, tfSponsorshipReassign, checkId),
1605 sponsor::As(sponsor2, spfSponsorReserve));
1606 env.close();
1607
1608 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
1609 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
1610 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0);
1611 BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
1612 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
1613 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
1614 BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
1615 BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
1616 BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0);
1617 checkSle = env.le(keylet::unchecked(checkId));
1618 BEAST_EXPECT(checkSle->isFieldPresent(sfSponsor));
1619 BEAST_EXPECT(checkSle->getAccountID(sfSponsor) == sponsor2.id());
1620 sponsor1Sle = env.le(keylet::sponsorship(sponsor1, alice));
1621 BEAST_EXPECT(sponsor1Sle->getFieldU32(sfRemainingOwnerCount) == 99);
1622 auto sponsor2Sle = env.le(keylet::sponsorship(sponsor2, alice));
1623 BEAST_EXPECT(sponsor2Sle->getFieldU32(sfRemainingOwnerCount) == 99);
1624
1625 // dissolve sponsor
1626 adjustAccountXRPBalance(env, alice, reserve(env, 1));
1627 env(sponsor::transfer(alice, tfSponsorshipEnd, checkId));
1628 env.close();
1629
1630 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
1631 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
1632 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0);
1633 BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
1634 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
1635 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
1636 BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
1637 BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
1638 BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0);
1639 BEAST_EXPECT(
1640 !env.le(keylet::account(sponsor2))->isFieldPresent(sfSponsoringOwnerCount));
1641 checkSle = env.le(keylet::unchecked(checkId));
1642 BEAST_EXPECT(!checkSle->isFieldPresent(sfSponsor));
1643 sponsor2Sle = env.le(keylet::sponsorship(sponsor2, alice));
1644 BEAST_EXPECT(sponsor2Sle->getFieldU32(sfRemainingOwnerCount) == 99);
1645 }
1646
1647 {
1648 // Dissolve object sponsorship from sponsor(no-ltSponsorship)
1649 Env env{*this, testableAmendments()};
1650 Account const alice("alice");
1651 Account const bob("bob");
1652 Account const sponsor("sponsor");
1653 env.fund(XRP(10000), alice, bob, sponsor);
1654 env.close();
1655
1656 auto const seq = env.seq(alice);
1657 env(check::create(alice, bob, XRP(1)));
1658 env.close();
1659
1660 auto const checkId = keylet::check(alice, SeqProxy::rawSequence(seq)).key;
1661 BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr);
1662
1663 env(sponsor::transfer(alice, tfSponsorshipCreate, checkId),
1665 Sig(sfSponsorSignature, sponsor));
1666 env.close();
1667
1668 BEAST_EXPECT(
1669 env.le(keylet::unchecked(checkId))->getAccountID(sfSponsor) == sponsor.id());
1670 BEAST_EXPECT(ownerCount(env, alice) == 1);
1671 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
1672 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
1673
1674 // not the owner of the object
1675 env(sponsor::transfer(sponsor, tfSponsorshipEnd, checkId), Ter(tecNO_PERMISSION));
1676 env.close();
1677
1678 env(sponsor::transfer(sponsor, tfSponsorshipEnd, checkId), sponsor::SponseeAcc(alice));
1679 env.close();
1680
1681 BEAST_EXPECT(!env.le(keylet::unchecked(checkId))->isFieldPresent(sfSponsor));
1682 BEAST_EXPECT(ownerCount(env, alice) == 1);
1683 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
1684 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
1685 }
1686
1687 {
1688 // Dissolve object sponsorship from sponsor (with ltSponsorship)
1689 Env env{*this, testableAmendments()};
1690 Account const alice("alice");
1691 Account const bob("bob");
1692 Account const sponsor("sponsor");
1693 env.fund(XRP(10000), alice, bob, sponsor);
1694 env.close();
1695
1696 auto const seq = env.seq(alice);
1697 env(check::create(alice, bob, XRP(1)));
1698 env.close();
1699
1700 auto const checkId = keylet::check(alice, SeqProxy::rawSequence(seq)).key;
1701 BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr);
1702
1703 env(sponsor::transfer(alice, tfSponsorshipCreate, checkId),
1705 Sig(sfSponsorSignature, sponsor));
1706 env.close();
1707
1708 env(sponsor::set_reserve(sponsor, 0, 100), sponsor::SponseeAcc(alice));
1709 env.close();
1710
1711 BEAST_EXPECT(
1712 env.le(keylet::unchecked(checkId))->getAccountID(sfSponsor) == sponsor.id());
1713 BEAST_EXPECT(ownerCount(env, alice) == 1);
1714 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
1715 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
1716 BEAST_EXPECT(
1717 env.le(keylet::sponsorship(sponsor, alice))->getFieldU32(sfRemainingOwnerCount) ==
1718 100);
1719
1720 // not the owner of the object
1721 env(sponsor::transfer(sponsor, tfSponsorshipEnd, checkId), Ter(tecNO_PERMISSION));
1722 env.close();
1723
1724 env(sponsor::transfer(sponsor, tfSponsorshipEnd, checkId), sponsor::SponseeAcc(alice));
1725 env.close();
1726
1727 BEAST_EXPECT(!env.le(keylet::unchecked(checkId))->isFieldPresent(sfSponsor));
1728 BEAST_EXPECT(ownerCount(env, alice) == 1);
1729 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
1730 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
1731 BEAST_EXPECT(
1732 env.le(keylet::sponsorship(sponsor, alice))->getFieldU32(sfRemainingOwnerCount) ==
1733 100);
1734 }
1735
1736 {
1737 // sponsor trustline
1738 Account const alice("alice");
1739 Account const bob("bob");
1740 Account const sponsor("sponsor");
1741
1742 auto const& highAcc = alice > bob ? alice : bob;
1743 auto const& lowAcc = alice > bob ? bob : alice;
1744
1745 for (bool const isIssuerHigh : {false, true})
1746 {
1747 Env env{*this, testableAmendments()};
1748 env.fund(XRP(10000), alice, bob, sponsor);
1749 env.close();
1750
1751 auto const& issuer = isIssuerHigh ? highAcc : lowAcc;
1752 auto const& user = isIssuerHigh ? lowAcc : highAcc;
1753
1754 auto const usd = issuer["usd"];
1755 auto const currency = usd.currency;
1756
1757 env(trust(user, issuer["usd"](100)));
1758 env.close();
1759
1760 auto const trustId = keylet::trustLine(user, issuer, currency);
1761 BEAST_EXPECT(env.le(trustId));
1762
1763 // transfer sponsor
1764 env(sponsor::transfer(user, tfSponsorshipCreate, trustId.key),
1766 Sig(sfSponsorSignature, sponsor));
1767 env.close();
1768
1769 BEAST_EXPECT(env.le(trustId));
1770
1771 BEAST_EXPECT(
1772 env.le(trustId)->getAccountID(isIssuerHigh ? sfLowSponsor : sfHighSponsor) ==
1773 sponsor.id());
1774 BEAST_EXPECT(
1775 !env.le(trustId)->isFieldPresent(isIssuerHigh ? sfHighSponsor : sfLowSponsor));
1776
1777 // dissolve sponsor
1778 env(sponsor::transfer(user, tfSponsorshipEnd, trustId.key));
1779 env.close();
1780
1781 BEAST_EXPECT(env.le(trustId));
1782 BEAST_EXPECT(
1783 !env.le(trustId)->isFieldPresent(isIssuerHigh ? sfLowSponsor : sfHighSponsor));
1784 BEAST_EXPECT(
1785 !env.le(trustId)->isFieldPresent(isIssuerHigh ? sfHighSponsor : sfLowSponsor));
1786 }
1787 }
1788
1789 {
1790 // invalid transfer
1791 Env env{*this, testableAmendments()};
1792 Account const alice("alice");
1793 Account const bob("bob");
1794 Account const sponsor("sponsor");
1795 env.fund(XRP(10000), alice, bob, sponsor);
1796 env.close();
1797
1798 // create owner dir
1799 env(ticket::create(alice, 1));
1800 env.close();
1801
1802 // AccountRoot
1803 // Amendments
1804 // LedgerHashes
1805 // FeeSettings
1806 // NegativeUNL
1807 // DirNode
1808 auto const keylets = {
1809 keylet::account(alice),
1810 // keylet::amendments(),
1811 keylet::skip(),
1813 // keylet::negativeUNL(),
1814 keylet::ownerDir(alice),
1815 };
1816 for (auto const& keylet : keylets)
1817 {
1818 env(sponsor::transfer(alice, tfSponsorshipCreate, keylet.key),
1820 Sig(sfSponsorSignature, sponsor),
1822 }
1823 }
1824
1825 {
1826 // existing owner objects that are outside the v1 SponsorshipTransfer
1827 // object allow-list
1828 Env env{*this, testableAmendments()};
1829 Account const alice("alice");
1830 Account const sponsor("sponsor");
1831 env.fund(XRP(10000), alice, sponsor);
1832 env.close();
1833
1834 auto const checkBlocked = [&](Account const& account, uint256 const& objectID) {
1835 env(sponsor::transfer(account, tfSponsorshipCreate, objectID),
1837 Sig(sfSponsorSignature, sponsor),
1839 env.close();
1840 };
1841
1842 auto const ticketSeq = env.seq(alice);
1843 env(ticket::create(alice, 1));
1844 env.close();
1845 auto const ticketID = keylet::ticket(alice, SeqProxy::rawTicket(ticketSeq + 1)).key;
1846 BEAST_EXPECT(env.le(keylet::unchecked(ticketID)));
1847 checkBlocked(alice, ticketID);
1848
1849 env(did::setValid(alice));
1850 env.close();
1851 auto const didKeylet = keylet::did(alice.id());
1852 BEAST_EXPECT(env.le(didKeylet));
1853 checkBlocked(alice, didKeylet.key);
1854
1855 env(token::mint(alice, 0u));
1856 env.close();
1857 auto const nftPageKeylet = keylet::nftokenPageMax(alice);
1858 BEAST_EXPECT(env.le(nftPageKeylet));
1859 checkBlocked(alice, nftPageKeylet.key);
1860
1861 Account const borrower("borrower");
1862 env.fund(XRP(1000000), borrower);
1863 env.close();
1864
1865 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
1866 Vault const vault{env};
1867 auto [vaultTx, vaultKeylet] = vault.create({.owner = alice, .asset = xrpAsset});
1868 env(vaultTx);
1869 env.close();
1870
1871 env(vault.deposit(
1872 {.depositor = alice, .id = vaultKeylet.key, .amount = xrpAsset(1000)}));
1873 env.close();
1874
1875 auto const brokerKeylet =
1876 keylet::loanBroker(alice.id(), SeqProxy::rawSequence(env.seq(alice)));
1877 env(loan_broker::set(alice, vaultKeylet.key),
1878 loan_broker::kDebtMaximum(xrpAsset(1000).value()),
1882 env.close();
1883
1884 auto const loanKeylet = keylet::loan(brokerKeylet.key, SeqProxy::rawSequence(1));
1885 env(loan::set(borrower, brokerKeylet.key, xrpAsset(100).value()),
1886 Sig(sfCounterpartySignature, alice),
1887 Fee(env.current()->fees().base * 2));
1888 env.close();
1889 BEAST_EXPECT(env.le(loanKeylet));
1890 checkBlocked(borrower, loanKeylet.key);
1891 }
1892 }
1893
1894 void
1896 {
1897 using namespace test::jtx;
1898
1899 testcase("Sponsor Fee");
1900
1901 {
1902 // co-signing
1903 Env env{*this, testableAmendments()};
1904 Account const alice("alice");
1905 Account const bob("bob");
1906 Account const sponsor("sponsor");
1907 env.fund(XRP(10000), alice, bob);
1908 env.close();
1909
1910 {
1911 // Fee should be checked before sponsor permission, otherwise a tec
1912 // result from a later check could cause context reset to pay Fee.
1913 auto aliceBalance = env.balance(alice);
1914 auto bobBalance = env.balance(bob);
1915 auto sponsorBalance = env.balance(sponsor);
1916
1917 env(pay(alice, bob, XRP(100)),
1918 Fee(XRP(2000)),
1920 Sig(sfSponsorSignature, sponsor),
1922 env.close();
1923 BEAST_EXPECT(env.balance(alice) == aliceBalance);
1924 BEAST_EXPECT(env.balance(bob) == bobBalance);
1925 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
1926 }
1927
1928 env.fund(XRP(1000), sponsor);
1929 env.close();
1930
1931 {
1932 // Sponsor pays the Fee
1933 auto aliceBalance = env.balance(alice);
1934 auto bobBalance = env.balance(bob);
1935 auto sponsorBalance = env.balance(sponsor);
1936
1937 auto const sendAmt = XRP(100);
1938 auto const feeAmt = XRP(10);
1939 env(pay(alice, bob, sendAmt),
1940 Fee(feeAmt),
1942 Sig(sfSponsorSignature, sponsor));
1943 env.close();
1944 BEAST_EXPECT(env.balance(alice) == aliceBalance - sendAmt);
1945 BEAST_EXPECT(env.balance(bob) == bobBalance + sendAmt);
1946 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance - feeAmt);
1947 }
1948
1949 {
1950 // insufficient balance to pay Fee
1951 auto aliceBalance = env.balance(alice);
1952 auto bobBalance = env.balance(bob);
1953 auto sponsorBalance = env.balance(sponsor);
1954
1955 env(pay(alice, bob, XRP(100)),
1956 Fee(XRP(2000)),
1958 Sig(sfSponsorSignature, sponsor),
1960 env.close();
1961 BEAST_EXPECT(env.balance(alice) == aliceBalance);
1962 BEAST_EXPECT(env.balance(bob) == bobBalance);
1963 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
1964 }
1965
1966 {
1967 // Fee is paid by Sponsor
1968 // on context reset (tec error)
1969 auto aliceBalance = env.balance(alice);
1970 auto bobBalance = env.balance(bob);
1971 auto sponsorBalance = env.balance(sponsor);
1972 auto const feeAmt = XRP(10);
1973
1974 env(pay(alice, bob, XRP(20000)),
1975 Fee(feeAmt),
1977 Sig(sfSponsorSignature, sponsor),
1979 env.close();
1980
1981 BEAST_EXPECT(env.balance(alice) == aliceBalance);
1982 BEAST_EXPECT(env.balance(bob) == bobBalance);
1983 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance - feeAmt);
1984 }
1985
1986 {
1987 // A co-signed sponsor pays the fee from its own balance, but
1988 // must never be charged into its own account reserve. With a
1989 // balance of exactly reserve + fee the fee is still payable,
1990 // charging the sponsor down to precisely its reserve.
1991 auto const feeAmt = XRP(10);
1992 adjustAccountXRPBalance(env, sponsor, reserve(env, 0) + feeAmt);
1993 auto const sponsorBalance = env.balance(sponsor);
1994
1995 env(noop(alice),
1996 Fee(feeAmt),
1998 Sig(sfSponsorSignature, sponsor),
1999 Ter(tesSUCCESS));
2000 env.close();
2001
2002 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance - feeAmt);
2003 BEAST_EXPECT(env.balance(sponsor) == reserve(env, 0));
2004 }
2005
2006 {
2007 // below reserve
2008 adjustAccountXRPBalance(env, sponsor, env.current()->fees().reserve);
2009
2010 env(noop(alice),
2011 Fee(env.current()->fees().base),
2013 Sig(sfSponsorSignature, sponsor),
2015 env.close();
2016
2017 env(noop(alice),
2018 Fee(XRP(10)),
2020 Sig(sfSponsorSignature, sponsor),
2022 env.close();
2023 }
2024 }
2025
2026 {
2027 // pre funded
2028 Env env{*this, testableAmendments()};
2029 Account const alice("alice");
2030 Account const bob("bob");
2031 Account const sponsor("sponsor");
2032 env.fund(XRP(10000), alice, bob, sponsor);
2033 env.close();
2034
2035 {
2036 // Fee should be checked before sponsor permission, otherwise a tec
2037 // result from a later check could cause context reset to pay Fee.
2038 auto aliceBalance = env.balance(alice);
2039 auto bobBalance = env.balance(bob);
2040 auto sponsorBalance = env.balance(sponsor);
2041
2042 env(pay(alice, bob, XRP(100)),
2043 Fee(XRP(2000)),
2046 env.close();
2047 BEAST_EXPECT(env.balance(alice) == aliceBalance);
2048 BEAST_EXPECT(env.balance(bob) == bobBalance);
2049 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
2050 }
2051
2052 env(sponsor::set_fee(sponsor, 0, XRP(100)), sponsor::SponseeAcc(alice));
2053 env.close();
2054
2055 {
2056 // Sponsor pays the Fee
2057 auto aliceBalance = env.balance(alice);
2058 auto bobBalance = env.balance(bob);
2059 auto sponsorBalance = env.balance(sponsor);
2060 auto sponsorFee = sponsor::sponsorshipFeeBalance(env, sponsor, alice);
2061
2062 auto const sendAmt = XRP(100);
2063 auto const feeAmt = XRP(10);
2064 env(pay(alice, bob, sendAmt), Fee(feeAmt), sponsor::As(sponsor, spfSponsorFee));
2065 env.close();
2066
2067 BEAST_EXPECT(env.balance(alice) == aliceBalance - sendAmt);
2068 BEAST_EXPECT(env.balance(bob) == bobBalance + sendAmt);
2069 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
2070 BEAST_EXPECT(
2071 sponsor::sponsorshipFeeBalance(env, sponsor, alice) == sponsorFee - feeAmt);
2072 }
2073
2074 {
2075 // insufficient balance to pay Fee
2076 {
2077 // > FeeAmount
2078 auto aliceBalance = env.balance(alice);
2079 auto bobBalance = env.balance(bob);
2080 auto sponsorBalance = env.balance(sponsor);
2081 auto sponsorFee = sponsor::sponsorshipFeeBalance(env, sponsor, alice);
2082
2083 env(pay(alice, bob, XRP(100)),
2084 Fee(XRP(90) + drops(1)),
2087 env.close();
2088
2089 BEAST_EXPECT(env.balance(alice) == aliceBalance);
2090 BEAST_EXPECT(env.balance(bob) == bobBalance);
2091 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
2092 BEAST_EXPECT(sponsor::sponsorshipFeeBalance(env, sponsor, alice) == sponsorFee);
2093 }
2094 // use all FeeAmount
2095 {
2096 // = FeeAmount
2097 auto aliceBalance = env.balance(alice);
2098 auto bobBalance = env.balance(bob);
2099 auto sponsorBalance = env.balance(sponsor);
2100
2101 env(pay(alice, bob, XRP(100)),
2102 Fee(XRP(90)),
2104 Ter(tesSUCCESS));
2105 env.close();
2106
2107 BEAST_EXPECT(env.balance(alice) == aliceBalance - XRP(100));
2108 BEAST_EXPECT(env.balance(bob) == bobBalance + XRP(100));
2109 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
2110 BEAST_EXPECT(
2111 !env.le(keylet::sponsorship(sponsor, alice))->isFieldPresent(sfFeeAmount));
2112 }
2113
2114 // reset FeeAmount and MaxFee
2116 env.close();
2117 env(sponsor::set_fee(sponsor, 0, XRP(10), XRP(1)), sponsor::SponseeAcc(alice));
2118 env.close();
2119
2120 {
2121 // > MaxFee
2122 auto aliceBalance = env.balance(alice);
2123 auto bobBalance = env.balance(bob);
2124 auto sponsorBalance = env.balance(sponsor);
2125 auto sponsorFee = sponsor::sponsorshipFeeBalance(env, sponsor, alice);
2126
2127 env(pay(alice, bob, XRP(100)),
2128 Fee(XRP(1) + drops(1)),
2131 env.close();
2132
2133 BEAST_EXPECT(env.balance(alice) == aliceBalance);
2134 BEAST_EXPECT(env.balance(bob) == bobBalance);
2135 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
2136 BEAST_EXPECT(sponsor::sponsorshipFeeBalance(env, sponsor, alice) == sponsorFee);
2137 }
2138 }
2139
2140 {
2141 // Fee is paid by Sponsor
2142 // on context reset (tec error)
2143 auto aliceBalance = env.balance(alice);
2144 auto bobBalance = env.balance(bob);
2145 auto sponsorBalance = env.balance(sponsor);
2146 auto sponsorFee = sponsor::sponsorshipFeeBalance(env, sponsor, alice);
2147 auto const feeAmt = XRP(1);
2148
2149 env(pay(alice, bob, XRP(20000)),
2150 Fee(feeAmt),
2153 env.close();
2154
2155 BEAST_EXPECT(env.balance(alice) == aliceBalance);
2156 BEAST_EXPECT(env.balance(bob) == bobBalance);
2157 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
2158 BEAST_EXPECT(
2159 sponsor::sponsorshipFeeBalance(env, sponsor, alice) == sponsorFee - feeAmt);
2160 }
2161
2162 // make sfFeeAmount absent if tec error and all Fee is paid
2163 {
2164 // reset FeeAmount and MaxFee
2166 env(sponsor::set_fee(sponsor, 0, XRP(10)), sponsor::SponseeAcc(alice));
2167 env.close();
2168
2169 BEAST_EXPECT(
2170 env.le(keylet::sponsorship(sponsor, alice))->isFieldPresent(sfFeeAmount));
2171 auto sponsorAvailableFee = sponsor::sponsorshipFeeBalance(env, sponsor, alice);
2172 env(check::cancel(alice, uint256(1)),
2173 Fee(sponsorAvailableFee),
2175 Ter(tecNO_ENTRY));
2176 env.close();
2177 BEAST_EXPECT(
2178 !env.le(keylet::sponsorship(sponsor, alice))->isFieldPresent(sfFeeAmount));
2179 }
2180 }
2181
2182 // MaxFee cap is enforced in reset() for tec-failing transactions.
2183 // On a closed ledger view (!view.open()), checkFee returns tecINSUFF_FEE when
2184 // Fee > MaxFee (not terINSUF_FEE_B), triggering reset()
2185 {
2186 Env env{*this, testableAmendments()};
2187 Account const alice("alice");
2188 Account const carol("sponsor");
2189
2190 env.fund(XRP(10000), alice, carol);
2191 env.close();
2192
2193 // FeeAmount=1000 drops, MaxFee=10 drops
2194 env(sponsor::set_fee(carol, 0, drops(1000), drops(10)), sponsor::SponseeAcc(alice));
2195 env.close();
2196
2197 // Apply directly against the closed ledger view (open_ = false) so that
2198 // checkFee returns tecINSUFF_FEE and reset() is invoked.
2199 OpenView overlay(&*env.closed());
2200
2201 auto jt = env.jt(
2202 noop(alice),
2203 Fee(drops(1000)),
2204 Seq(env.seq(alice)),
2205 sponsor::As(carol, spfSponsorFee));
2206
2207 auto const result = xrpl::apply(env.app(), overlay, *jt.stx, TapNone, env.journal);
2208 BEAST_EXPECT(result.ter == tecINSUFF_FEE);
2209 BEAST_EXPECT(result.applied);
2210
2211 // Only MaxFee (10 drops) must be deducted, not the full 1000 drops.
2212 auto const sle = overlay.read(keylet::sponsorship(carol.id(), alice.id()));
2213 BEAST_EXPECT(sle);
2214 BEAST_EXPECT(sle->isFieldPresent(sfFeeAmount));
2215 BEAST_EXPECT(sle->getFieldAmount(sfFeeAmount) == drops(990)); // 1000 - MaxFee(10)
2216 }
2217
2218 // LedgerStateFix charges an owner-reserve fee and can claim that fee
2219 // while returning tecFAILED_PROCESSING. That path must be safe when the
2220 // fee is pre-funded by a sponsorship object.
2221 {
2222 Env env{*this, testableAmendments()};
2223 Account const alice("alice");
2224 Account const sponsor("sponsor");
2225 env.fund(XRP(1000), alice, sponsor);
2226 env.close();
2227
2228 auto const fixFee = drops(env.current()->fees().increment);
2229 env(sponsor::set_fee(sponsor, 0, fixFee), sponsor::SponseeAcc(alice));
2230 env.close();
2231
2232 env(ledger_state_fix::nftPageLinks(alice, alice),
2233 Fee(fixFee),
2236
2237 if (auto const sle = env.le(keylet::sponsorship(sponsor, alice)); BEAST_EXPECT(sle))
2238 BEAST_EXPECT(!sle->isFieldPresent(sfFeeAmount));
2239 }
2240
2241 // If preclaim saw spendable sponsored FeeAmount but the apply view no
2242 // longer has it, the fee path should fail cleanly instead of throwing.
2243 {
2244 Env env{*this, testableAmendments()};
2245 Account const alice("alice");
2246 Account const sponsor("sponsor");
2247 env.fund(XRP(1000), alice, sponsor);
2248 env.close();
2249
2250 auto const fixFee = drops(env.current()->fees().increment);
2251 env(sponsor::set_fee(sponsor, 0, fixFee), sponsor::SponseeAcc(alice));
2252 env.close();
2253
2254 OpenView overlay(&*env.closed());
2255 auto jt = env.jt(
2256 ledger_state_fix::nftPageLinks(alice, alice),
2257 Fee(fixFee),
2259
2260 auto const pf = preflight(env.app(), overlay.rules(), *jt.stx, TapNone, env.journal);
2261 BEAST_EXPECT(isTesSuccess(pf.ter));
2262 auto const pc = preclaim(pf, env.app(), overlay);
2263 BEAST_EXPECT(isTesSuccess(pc.ter));
2264
2265 auto const original = overlay.read(keylet::sponsorship(sponsor, alice));
2266 if (BEAST_EXPECT(original))
2267 {
2268 auto sle = std::make_shared<SLE>(*original);
2269 sle->makeFieldAbsent(sfFeeAmount);
2270 overlay.rawReplace(sle);
2271 }
2272
2273 auto const result = doApply(pc, env.app(), overlay);
2274 BEAST_EXPECT(result.ter == terINSUF_FEE_B);
2275 BEAST_EXPECT(!result.applied);
2276 }
2277
2278 // test lsfSponsorshipRequireSignForFee
2279 {
2280 Env env{*this, testableAmendments()};
2281 Account const alice("alice");
2282 Account const bob("bob");
2283 Account const sponsor("sponsor");
2284 env.fund(XRP(10000), alice, bob, sponsor);
2285 env.close();
2286
2287 // set flag
2288 env(sponsor::set_fee(sponsor, tfSponsorshipSetRequireSignForFee, XRP(10)),
2289 sponsor::SponseeAcc(alice));
2290 env.close();
2291
2292 env(pay(alice, bob, XRP(100)),
2293 Fee(XRP(10)),
2296 env.close();
2297
2298 BEAST_EXPECT(
2299 env.le(keylet::sponsorship(sponsor, alice))->getFieldAmount(sfFeeAmount) ==
2300 XRP(10));
2301
2302 // clear flag
2303 env(sponsor::set(sponsor, tfSponsorshipClearRequireSignForFee),
2304 sponsor::SponseeAcc(alice));
2305 env.close();
2306
2307 // Payment is re-applied
2308 BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice))->isFieldPresent(sfFeeAmount));
2309 }
2310
2311 // RequireSignForFee: co-signing should succeed
2312 {
2313 Env env{*this, testableAmendments()};
2314 Account const alice("alice");
2315 Account const bob("bob");
2316 Account const sponsor("sponsor");
2317 env.fund(XRP(10000), alice, bob, sponsor);
2318 env.close();
2319
2320 // set flag
2321 env(sponsor::set_fee(sponsor, tfSponsorshipSetRequireSignForFee, XRP(10)),
2322 sponsor::SponseeAcc(alice));
2323 env.close();
2324
2325 // pre-funded (no sig) should fail
2326 env(pay(alice, bob, XRP(100)),
2327 Fee(XRP(1)),
2330 env.close();
2331
2332 // co-signing (with sig) should succeed
2333 env(pay(alice, bob, XRP(100)),
2334 Fee(XRP(1)),
2336 Sig(sfSponsorSignature, sponsor),
2337 Ter(tesSUCCESS));
2338 env.close();
2339
2340 BEAST_EXPECT(
2341 env.le(keylet::sponsorship(sponsor, alice))->getFieldAmount(sfFeeAmount) == XRP(9));
2342 }
2343 }
2344
2345 void
2347 {
2348 testcase("Sponsor Account");
2349 using namespace test::jtx;
2350
2351 Account const alice("alice");
2352 Account const sponsor("sponsor");
2353 Account const sponsor2("sponsor2");
2354 Account const sponsor3("sponsor3");
2355 Account const bob("bob");
2356 Account const charlie("charlie");
2357 Account const dave("dave");
2358 Account const gw("gw");
2359 auto const usd = gw["usd"];
2360
2361 {
2362 // Disabled
2363 Env env{*this, testableAmendments() - featureSponsor};
2364 env.fund(XRP(10000), alice, sponsor);
2365 env.close();
2366 env(pay(alice, bob, XRP(100)), Txflags(tfSponsorCreatedAccount), Ter(temDISABLED));
2367 env.close();
2368 }
2369
2370 Env env{*this, testableAmendments()};
2371 env.fund(XRP(10000), alice, sponsor, sponsor2, sponsor3);
2372 env.close();
2373
2374 // Invalid flags
2375 for (auto flag : {
2376 tfNoRippleDirect,
2377 tfPartialPayment,
2378 tfLimitQuality,
2379 })
2380 {
2381 env(pay(alice, bob, XRP(100)),
2382 Txflags(tfSponsorCreatedAccount | flag),
2384 env.close();
2385 }
2386
2387 // Invalid amount(iou)
2388 env(pay(alice, bob, usd(100)), Txflags(tfSponsorCreatedAccount), Ter(temBAD_AMOUNT));
2389 env.close();
2390
2391 // Sponsored account creation is reserve sponsorship and is only supported for direct XRP
2392 // payments.
2393 env(pay(alice, bob, drops(1)),
2394 Txflags(tfSponsorCreatedAccount),
2395 Sendmax(usd(2)),
2396 Ter(temINVALID));
2397 env.close();
2398
2399 env(pay(alice, bob, drops(1)),
2400 Txflags(tfSponsorCreatedAccount),
2401 Path(~XRP),
2402 Ter(temINVALID));
2403 env.close();
2404
2405 // Account is not sponsored by normal Sponsor specification
2406 {
2407 env(pay(alice, bob, drops(env.current()->fees().accountReserve(0, 1))),
2409 Sig(sfSponsorSignature, sponsor));
2410 env.close();
2411
2412 auto const bobSle = env.le(keylet::account(bob));
2413 BEAST_EXPECT(!bobSle->isFieldPresent(sfSponsor));
2414 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
2415 BEAST_EXPECT(sponsoringAccountCount(env, sponsor) == 0);
2416 }
2417
2418 // Use tfSponsorCreatedAccount to sponsor an account
2419 {
2420 // to funded account
2421 env(pay(sponsor2, bob, drops(1)),
2422 Txflags(tfSponsorCreatedAccount),
2423 Fee(XRP(1)),
2425 env.close();
2426
2427 BEAST_EXPECT(env.balance(sponsor2) == XRP(9999));
2428
2429 // to non-funded account / insufficient balance for reserve
2430 env(pay(sponsor2, charlie, XRP(9999) - env.current()->fees().reserve + drops(1)),
2431 Txflags(tfSponsorCreatedAccount),
2433 env.close();
2434
2435 // to non-funded account
2436 auto const sponsor2BalanceBefore = env.balance(sponsor2);
2437 env(pay(sponsor2, charlie, drops(1)), Txflags(tfSponsorCreatedAccount), Fee(XRP(1)));
2438 env.close();
2439
2440 auto const charlieSle = env.le(keylet::account(charlie));
2441 BEAST_EXPECT(charlieSle->isFieldPresent(sfSponsor));
2442 BEAST_EXPECT(charlieSle->getAccountID(sfSponsor) == sponsor2.id());
2443 BEAST_EXPECT(sponsoredOwnerCount(env, charlie) == 0);
2444 BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 1);
2445 // verify sponsor balance decreased by payment + Fee
2446 BEAST_EXPECT(env.balance(sponsor2) == sponsor2BalanceBefore - drops(1) - XRP(1));
2447 }
2448 {
2449 // insufficient reserve to sponsor acount
2450
2451 auto const sendAmount = drops(1);
2452 // 2 account reserve + send amount
2453 auto const requireBalance = accountReserve(env, 2) + sendAmount;
2454 adjustAccountXRPBalance(env, sponsor3, requireBalance - drops(1));
2455 env(pay(sponsor3, dave, sendAmount),
2456 Txflags(tfSponsorCreatedAccount),
2457 Fee(XRP(1)),
2459 env.close();
2460
2461 adjustAccountXRPBalance(env, sponsor3, requireBalance);
2462 env(pay(sponsor3, dave, sendAmount),
2463 Txflags(tfSponsorCreatedAccount),
2464 Fee(XRP(1)),
2465 Ter(tesSUCCESS));
2466 env.close();
2467 }
2468 }
2469
2470 void
2472 {
2473 using namespace test::jtx;
2474 {
2475 testcase("SponsorshipRequireSignForReserve");
2476
2477 Env env{*this, testableAmendments()};
2478 Account const alice("alice");
2479 Account const bob("bob");
2480 Account const sponsor("sponsor");
2481 env.fund(XRP(10000), alice, bob, sponsor);
2482 env.close();
2483
2484 // set flag
2485 env(sponsor::set_reserve(sponsor, tfSponsorshipSetRequireSignForReserve, 10),
2486 sponsor::SponseeAcc(alice));
2487 env.close();
2488
2489 env(check::create(alice, bob, XRP(100)),
2490 Fee(XRP(10)),
2493
2494 BEAST_EXPECT(ownerCount(env, alice) == 0);
2495 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
2496 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
2497
2498 // clear flag
2499 env(sponsor::set_reserve(sponsor, tfSponsorshipClearRequireSignForReserve, 1),
2500 sponsor::SponseeAcc(alice));
2501 env.close();
2502
2503 // CheckCreate is re-applied
2504 BEAST_EXPECT(ownerCount(env, alice) == 1);
2505 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
2506 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
2507 }
2508
2509 {
2510 testcase("SponsorshipRequireSignForFee");
2511
2512 Env env{*this, testableAmendments()};
2513 Account const alice("alice");
2514 Account const bob("bob");
2515 Account const sponsor("sponsor");
2516 env.fund(XRP(10000), alice, bob, sponsor);
2517 env.close();
2518
2519 // set flag
2520 env(sponsor::set_fee(sponsor, tfSponsorshipSetRequireSignForFee, XRP(10)),
2521 sponsor::SponseeAcc(alice));
2522 env.close();
2523
2524 env(check::create(alice, bob, XRP(100)),
2525 Fee(XRP(10)),
2528
2529 BEAST_EXPECT(ownerCount(env, alice) == 0);
2530 BEAST_EXPECT(
2531 env.le(keylet::sponsorship(sponsor, alice))->getFieldAmount(sfFeeAmount) ==
2532 XRP(10));
2533
2534 // clear flag
2535 env(sponsor::set(sponsor, tfSponsorshipClearRequireSignForFee),
2536 sponsor::SponseeAcc(alice));
2537 env.close();
2538
2539 // CheckCreate is re-applied
2540 BEAST_EXPECT(ownerCount(env, alice) == 1);
2541 BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice))->isFieldPresent(sfFeeAmount));
2542 }
2543 }
2544
2545 void
2547 {
2548 testcase("SponsorReserveSimple");
2549 using namespace test::jtx;
2550 Env env{*this, testableAmendments()};
2551 Account const alice("alice");
2552 Account const bob("bob");
2553 Account const sponsor("sponsor");
2554
2555 env.fund(XRP(10000), alice, bob, sponsor);
2556 env.close();
2557
2558 if (cosigning)
2559 {
2560 adjustAccountXRPBalance(env, sponsor, reserve(env, 1) - drops(1));
2561
2562 env(check::create(alice, bob, XRP(100)),
2564 Sig(sfSponsorSignature, sponsor),
2566 env.close();
2567
2569
2570 env(check::create(alice, bob, XRP(100)),
2572 Sig(sfSponsorSignature, sponsor),
2573 Ter(tesSUCCESS));
2574 env.close();
2575 }
2576 else
2577 {
2578 env(sponsor::set_reserve(sponsor, 0, 250), sponsor::SponseeAcc(alice));
2579 env.close();
2580
2581 adjustAccountXRPBalance(env, sponsor, reserve(env, 2) - drops(1));
2582
2583 env(check::create(alice, bob, XRP(100)),
2586 env.close();
2587
2589
2590 env(check::create(alice, bob, XRP(100)),
2592 Ter(tesSUCCESS));
2593 env.close();
2594 }
2595 }
2596
2597 // test helper for both cosigning and pre-funded sponsorship
2598 template <typename SubmitCallback>
2599 void
2601 test::jtx::Env& env,
2602 bool cosigning,
2603 jtx::Account const& sponsor,
2604 jtx::Account const& sponsee,
2605 uint32_t reserveCount,
2606 uint32_t sponsorReserveCount,
2607 TER insufficientReserveResult,
2608 SubmitCallback callback,
2609 std::optional<std::function<void()>> expected = std::nullopt)
2610 {
2611 using namespace test::jtx;
2612 auto const sponseeOwnerCountBefore = ownerCount(env, sponsee);
2613 auto const sponseeSponsoredOwnerCountBefore = sponsoredOwnerCount(env, sponsee);
2614 auto const sponseeSponsoringOwnerCountBefore = sponsoringOwnerCount(env, sponsee);
2615 auto const sponsorOwnerCountBefore = ownerCount(env, sponsor);
2616 auto const sponsorSponsoredOwnerCountBefore = sponsoredOwnerCount(env, sponsor);
2617 auto const sponsorSponsoringOwnerCountBefore = sponsoringOwnerCount(env, sponsor);
2618
2619 std::optional<Sig> sponsorSig =
2620 cosigning ? std::optional<Sig>(Sig(sfSponsorSignature, sponsor)) : std::nullopt;
2621
2622 auto const sponsorCurrentOwnerCount = ownerCount(env, sponsor) -
2624
2625 auto submit = [&](TER ter) {
2626 return [&, ter](json::Value const& jv, auto const&... fN) {
2627 if (sponsorSig)
2628 {
2629 env(jv, fN..., sponsor::As(sponsor, spfSponsorReserve), *sponsorSig, Ter(ter));
2630 }
2631 else
2632 {
2633 env(jv, fN..., sponsor::As(sponsor, spfSponsorReserve), Ter(ter));
2634 }
2635 };
2636 };
2637
2638 // Insufficient Reserve
2639 {
2640 if (cosigning)
2641 {
2643 env,
2644 sponsor,
2645 reserve(env, sponsorCurrentOwnerCount + sponsorReserveCount) - drops(1));
2646 }
2647 else
2648 {
2649 // cleanup previous sponsorship
2650 if (env.le(keylet::sponsorship(sponsor, sponsee)))
2651 {
2653 env.close();
2654 }
2655
2656 if (sponsorReserveCount > 1)
2657 {
2658 env(sponsor::set(sponsor, 0, sponsorReserveCount - 1, XRP(1)),
2659 sponsor::SponseeAcc(sponsee));
2660 }
2661 else
2662 {
2663 // just create sponsor object
2664 env(sponsor::set(sponsor, 0, std::nullopt, XRP(1)),
2665 sponsor::SponseeAcc(sponsee));
2666 }
2667 env.close();
2668 }
2669
2670 // A failed sponsored create must not consume prefunded reserve or mutate owner counts.
2671 auto const sponseeOwnerCountBeforeAttempt = ownerCount(env, sponsee);
2672 auto const sponseeSponsoredOwnerCountBeforeAttempt = sponsoredOwnerCount(env, sponsee);
2673 auto const sponseeSponsoringOwnerCountBeforeAttempt =
2674 sponsoringOwnerCount(env, sponsee);
2675 auto const sponsorOwnerCountBeforeAttempt = ownerCount(env, sponsor);
2676 auto const sponsorSponsoredOwnerCountBeforeAttempt = sponsoredOwnerCount(env, sponsor);
2677 auto const sponsorSponsoringOwnerCountBeforeAttempt =
2679 auto const sponsorshipSleBeforeAttempt = env.le(keylet::sponsorship(sponsor, sponsee));
2680 bool const reserveCountPresentBeforeAttempt = sponsorshipSleBeforeAttempt &&
2681 sponsorshipSleBeforeAttempt->isFieldPresent(sfRemainingOwnerCount);
2682 std::uint32_t const reserveCountBeforeAttempt = reserveCountPresentBeforeAttempt
2683 ? sponsorshipSleBeforeAttempt->getFieldU32(sfRemainingOwnerCount)
2684 : 0;
2685
2686 callback(env, submit(insufficientReserveResult));
2687 env.close();
2688
2689 BEAST_EXPECT(ownerCount(env, sponsee) == sponseeOwnerCountBeforeAttempt);
2690 BEAST_EXPECT(
2691 sponsoredOwnerCount(env, sponsee) == sponseeSponsoredOwnerCountBeforeAttempt);
2692 BEAST_EXPECT(
2693 sponsoringOwnerCount(env, sponsee) == sponseeSponsoringOwnerCountBeforeAttempt);
2694 BEAST_EXPECT(ownerCount(env, sponsor) == sponsorOwnerCountBeforeAttempt);
2695 BEAST_EXPECT(
2696 sponsoredOwnerCount(env, sponsor) == sponsorSponsoredOwnerCountBeforeAttempt);
2697 BEAST_EXPECT(
2698 sponsoringOwnerCount(env, sponsor) == sponsorSponsoringOwnerCountBeforeAttempt);
2699
2700 auto const sponsorshipSleAfterAttempt = env.le(keylet::sponsorship(sponsor, sponsee));
2701 BEAST_EXPECT(
2702 static_cast<bool>(sponsorshipSleAfterAttempt) ==
2703 static_cast<bool>(sponsorshipSleBeforeAttempt));
2704 if (sponsorshipSleAfterAttempt)
2705 {
2706 BEAST_EXPECT(
2707 sponsorshipSleAfterAttempt->isFieldPresent(sfRemainingOwnerCount) ==
2708 reserveCountPresentBeforeAttempt);
2709 if (reserveCountPresentBeforeAttempt)
2710 {
2711 BEAST_EXPECT(
2712 sponsorshipSleAfterAttempt->getFieldU32(sfRemainingOwnerCount) ==
2713 reserveCountBeforeAttempt);
2714 }
2715 }
2716 }
2717
2718 // Success
2719 {
2720 if (cosigning)
2721 {
2723 env, sponsor, reserve(env, sponsorCurrentOwnerCount + sponsorReserveCount));
2724 }
2725 else
2726 {
2727 // reset sponsorship
2729 env(sponsor::set(sponsor, 0, sponsorReserveCount, XRP(1)),
2730 sponsor::SponseeAcc(sponsee));
2731 env.close();
2732 }
2733 callback(env, submit(tesSUCCESS));
2734 env.close();
2735
2736 if (!cosigning)
2737 {
2738 // Prefunded success consumes the reserved owner slot before cleanup.
2739 auto const sponsorshipSle = env.le(keylet::sponsorship(sponsor, sponsee));
2740 BEAST_EXPECT(sponsorshipSle);
2741 BEAST_EXPECT(
2742 !sponsorshipSle->isFieldPresent(sfRemainingOwnerCount) ||
2743 sponsorshipSle->getFieldU32(sfRemainingOwnerCount) == 0);
2744
2745 // cleanup sponsorship
2747 env.close();
2748 }
2749 }
2750
2751 if (expected)
2752 {
2753 (*expected)();
2754 }
2755 else
2756 {
2757 BEAST_EXPECT(ownerCount(env, sponsee) - sponseeOwnerCountBefore == reserveCount);
2758 BEAST_EXPECT(
2759 sponsoredOwnerCount(env, sponsee) - sponseeSponsoredOwnerCountBefore ==
2760 sponsorReserveCount);
2761 BEAST_EXPECT(
2762 sponsoringOwnerCount(env, sponsee) - sponseeSponsoringOwnerCountBefore == 0);
2763 BEAST_EXPECT(ownerCount(env, sponsor) == sponsorOwnerCountBefore);
2764 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor) == sponsorSponsoredOwnerCountBefore);
2765 BEAST_EXPECT(
2766 sponsoringOwnerCount(env, sponsor) - sponsorSponsoringOwnerCountBefore ==
2767 sponsorReserveCount);
2768 }
2769 };
2770
2771 void
2772 testCheck(bool cosigning)
2773 {
2774 testcase("Check");
2775 using namespace test::jtx;
2776 Account const alice("alice");
2777 Account const bob("bob");
2778 Account const gw("gw");
2779 Account const sponsor("sponsor");
2780 Account const sponsor2("sponsor2");
2781
2782 auto const usd = gw["usd"];
2783
2784 {
2785 Env env{*this, testableAmendments()};
2786 env.fund(XRP(10000), alice, bob, sponsor, sponsor2);
2787 env.close();
2788
2789 // CheckCreate -> Check -> CheckCancel
2790
2791 uint32_t seq = 0;
2793 env,
2794 cosigning,
2795 sponsor,
2796 alice,
2797 1,
2798 1,
2800 [&](Env& env, auto const& submit) {
2801 seq = env.seq(alice);
2802 submit(check::create(alice, bob, XRP(1)));
2803 });
2804
2805 BEAST_EXPECT(ownerCount(env, alice) == 1); // Check
2806 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
2807 BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
2808 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
2809
2810 auto const keylet = keylet::check(alice, SeqProxy::rawSequence(seq));
2811 BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor.id());
2812
2813 if (cosigning)
2814 {
2815 // transfer sponsor
2816 env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
2817 sponsor::As(sponsor2, spfSponsorReserve),
2818 Sig(sfSponsorSignature, sponsor2));
2819 env.close();
2820 }
2821 else
2822 {
2823 env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice));
2824 env.close();
2825
2826 // transfer sponsor
2827 env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
2828 sponsor::As(sponsor2, spfSponsorReserve));
2829 env.close();
2830 }
2831
2832 BEAST_EXPECT(ownerCount(env, alice) == 1); // Check
2833 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
2834 BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
2835 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
2836 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
2837
2838 BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor2.id());
2839
2840 // CheckCancel
2841 env(check::cancel(alice, keylet.key));
2842 env.close();
2843
2844 BEAST_EXPECT(ownerCount(env, alice) == 0);
2845 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
2846 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
2847 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
2848 }
2849
2850 {
2851 Env env{*this, testableAmendments()};
2852 env.fund(XRP(10000), alice, bob, sponsor);
2853 env.close();
2854
2855 // CheckCreate -> CheckCash
2856 uint32_t seq2 = 0;
2858 env,
2859 cosigning,
2860 sponsor,
2861 alice,
2862 1,
2863 1,
2865 [&](Env& env, auto const& submit) {
2866 seq2 = env.seq(alice);
2867 submit(check::create(alice, bob, XRP(1)));
2868 });
2869
2870 BEAST_EXPECT(ownerCount(env, bob) == 0);
2871 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
2872
2873 // CheckCash
2874 auto const checkId2 = keylet::check(alice, SeqProxy::rawSequence(seq2)).key;
2875 env(check::cash(bob, checkId2, XRP(1)));
2876 env.close();
2877
2878 BEAST_EXPECT(ownerCount(env, alice) == 0);
2879 BEAST_EXPECT(ownerCount(env, bob) == 0);
2880 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
2881 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
2882 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
2883 }
2884
2885 // RippleState sponsor (CheckCashMakesTrustLine)
2886 {
2887 Env env{*this, testableAmendments()};
2888 env.fund(XRP(10000), alice, bob, gw, sponsor, sponsor2);
2889 env.close();
2890
2891 env.trust(usd(100), alice);
2892 env.close();
2893 env(pay(gw, alice, usd(100)));
2894 env.close();
2895
2896 // CheckCreate -> CheckCash
2897 uint32_t seq2 = 0;
2899 env,
2900 cosigning,
2901 sponsor,
2902 alice,
2903 1,
2904 1,
2906 [&](Env& env, auto const& submit) {
2907 seq2 = env.seq(alice);
2908 submit(check::create(alice, bob, usd(1)));
2909 });
2910
2911 BEAST_EXPECT(ownerCount(env, bob) == 0);
2912 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
2913
2914 auto const keylet = keylet::check(alice, SeqProxy::rawSequence(seq2));
2915 BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor.id());
2916
2917 // CheckCash
2919 env,
2920 cosigning,
2921 sponsor,
2922 bob,
2923 1,
2924 1,
2926 [&](Env& env, auto const& submit) { submit(check::cash(bob, keylet.key, usd(1))); },
2927 [&]() {
2928 BEAST_EXPECT(ownerCount(env, alice) == 1); // RippleState
2929 BEAST_EXPECT(ownerCount(env, bob) == 1); // RippleState
2930 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
2931 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1);
2932 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
2933
2934 // the newly-created trust line carries the sponsor on
2935 // bob's side, and no sponsor on the counterparty side
2936 bool const bobLow = bob.id() < gw.id();
2937 auto const lineSle = env.le(keylet::trustLine(bob, gw, usd.currency));
2938 BEAST_EXPECT(lineSle);
2939 if (lineSle)
2940 {
2941 BEAST_EXPECT(
2942 lineSle->getAccountID(bobLow ? sfLowSponsor : sfHighSponsor) ==
2943 sponsor.id());
2944 BEAST_EXPECT(
2945 !lineSle->isFieldPresent(bobLow ? sfHighSponsor : sfLowSponsor));
2946 }
2947 });
2948 }
2949
2950 // MPT sponsor: cashing an MPT check creates a sponsored MPToken for
2951 // the casher. Unlike the trust-line path, this returns
2952 // tecINSUFFICIENT_RESERVE (not tecNO_LINE_INSUF_RESERVE) on shortfall.
2953 {
2954 Env env{*this, testableAmendments()};
2955 env.fund(XRP(10000), bob, sponsor, sponsor2);
2956 env.close();
2957
2958 MPTTester mptGw(env, gw, {.holders = {alice}});
2959 mptGw.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer});
2960 mptGw.authorize({.account = alice});
2961 auto const mpt = mptGw["MPT"];
2962 env(pay(gw, alice, mpt(10'000)));
2963 env.close();
2964
2965 // CheckCreate (alice -> bob) paying the MPT
2966 uint32_t seq2 = 0;
2968 env,
2969 cosigning,
2970 sponsor,
2971 alice,
2972 1,
2973 1,
2975 [&](Env& env, auto const& submit) {
2976 seq2 = env.seq(alice);
2977 submit(check::create(alice, bob, mpt(1)));
2978 });
2979
2980 auto const checkKeylet = keylet::check(alice, SeqProxy::rawSequence(seq2));
2981 BEAST_EXPECT(env.le(checkKeylet)->getAccountID(sfSponsor) == sponsor.id());
2982 BEAST_EXPECT(ownerCount(env, bob) == 0);
2983
2984 // CheckCash by bob (no MPToken yet) creates a sponsored MPToken
2986 env,
2987 cosigning,
2988 sponsor,
2989 bob,
2990 1,
2991 1,
2993 [&](Env& env, auto const& submit) {
2994 submit(check::cash(bob, checkKeylet.key, mpt(1)));
2995 },
2996 [&]() {
2997 BEAST_EXPECT(ownerCount(env, bob) == 1); // MPToken
2998 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1);
2999 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
3000 BEAST_EXPECT(
3001 env.le(keylet::mptoken(mptGw.issuanceID(), bob))->getAccountID(sfSponsor) ==
3002 sponsor.id());
3003 });
3004 }
3005 }
3006
3007 void
3008 testDelegate(bool cosigning)
3009 {
3010 testcase("Delegate");
3011 using namespace test::jtx;
3012 Account const alice("alice");
3013 Account const bob("bob");
3014 Account const sponsor("sponsor");
3015 Account const sponsor2("sponsor2");
3016
3017 {
3018 Env env{*this, testableAmendments()};
3019 env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
3020 env.close();
3021
3022 // DelegateSet
3024 env,
3025 cosigning,
3026 sponsor,
3027 alice,
3028 1,
3029 1,
3031 [&](Env& env, auto const& submit) {
3032 submit(delegate::set(alice, bob, {"Payment"}));
3033 });
3034
3035 // the created Delegate object carries the sponsor
3036 auto const keylet = keylet::delegate(alice, bob);
3037 BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor.id());
3038
3039 // transfer sponsor
3040 if (cosigning)
3041 {
3042 env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
3043 sponsor::As(sponsor2, spfSponsorReserve),
3044 Sig(sfSponsorSignature, sponsor2));
3045 env.close();
3046 }
3047 else
3048 {
3049 env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice));
3050 env.close();
3051
3052 env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
3053 sponsor::As(sponsor2, spfSponsorReserve));
3054 env.close();
3055 }
3056
3057 BEAST_EXPECT(ownerCount(env, alice) == 1);
3058 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
3059 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3060 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
3061 BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor2.id());
3062
3063 // delete
3064 env(delegate::set(alice, bob, {}));
3065 env.close();
3066
3067 BEAST_EXPECT(ownerCount(env, alice) == 0);
3068 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
3069 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3070 }
3071 }
3072
3073 void
3074 testDepositPreauth(bool cosigning)
3075 {
3076 testcase("DepositPreauth");
3077 using namespace test::jtx;
3078 Account const alice("alice");
3079 Account const sponsor("sponsor");
3080 Account const sponsor2("sponsor2");
3081 auto const credType = std::string("credType");
3082
3083 {
3084 Env env{*this, testableAmendments()};
3085 env.fund(XRP(1000000), alice, sponsor, sponsor2);
3086 env.close();
3087
3088 // DepositPreauthSet
3090 env,
3091 cosigning,
3092 sponsor,
3093 alice,
3094 1,
3095 1,
3097 [&](Env& env, auto const& submit) { submit(deposit::auth(alice, sponsor)); });
3098
3099 // the created DepositPreauth object carries the sponsor
3100 auto const keylet = keylet::depositPreauth(alice, sponsor);
3101 BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor.id());
3102
3103 // transfer sponsor
3104 if (cosigning)
3105 {
3106 env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
3107 sponsor::As(sponsor2, spfSponsorReserve),
3108 Sig(sfSponsorSignature, sponsor2));
3109 env.close();
3110 }
3111 else
3112 {
3113 env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice));
3114 env.close();
3115 // No sponsor signature here: this exercises the prefunded reassign path.
3116 env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
3117 sponsor::As(sponsor2, spfSponsorReserve));
3118 env.close();
3119
3120 auto const sponsor2Sle = env.le(keylet::sponsorship(sponsor2, alice));
3121 BEAST_EXPECT(sponsor2Sle);
3122 if (sponsor2Sle)
3123 {
3124 BEAST_EXPECT(
3125 !sponsor2Sle->isFieldPresent(sfRemainingOwnerCount) ||
3126 sponsor2Sle->getFieldU32(sfRemainingOwnerCount) == 0);
3127 }
3128 }
3129
3130 BEAST_EXPECT(ownerCount(env, alice) == 1);
3131 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
3132 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3133 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
3134 BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor2.id());
3135
3136 // DepositPreauthDelete
3137 env(deposit::unauth(alice, sponsor));
3138 env.close();
3139
3140 BEAST_EXPECT(ownerCount(env, alice) == 0);
3141 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
3142 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3143 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
3144 }
3145
3146 {
3147 Env env{*this, testableAmendments()};
3148 env.fund(XRP(1000000), alice, sponsor);
3149 env.close();
3150 auto const authCreds = std::vector<deposit::AuthorizeCredentials>{
3151 {.issuer = sponsor, .credType = credType}};
3152 auto const preauthKeylet = keylet::depositPreauth(
3153 alice.id(),
3155 {sponsor.id(), Slice(credType.data(), credType.size())}});
3156
3157 // Cover DepositPreauth's sfAuthorizeCredentials sponsor-reserve branch.
3159 env,
3160 cosigning,
3161 sponsor,
3162 alice,
3163 1,
3164 1,
3166 [&](Env&, auto const& submit) {
3167 submit(deposit::authCredentials(alice, authCreds));
3168 });
3169
3170 // Cover sfUnauthorizeCredentials cleanup for a sponsored preauth object.
3171 BEAST_EXPECT(env.le(preauthKeylet));
3172 BEAST_EXPECT(env.le(preauthKeylet)->getAccountID(sfSponsor) == sponsor.id());
3173 BEAST_EXPECT(ownerCount(env, alice) == 1);
3174 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
3175 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
3176
3177 env(deposit::unauthCredentials(alice, authCreds));
3178 env.close();
3179
3180 BEAST_EXPECT(!env.le(preauthKeylet));
3181 BEAST_EXPECT(ownerCount(env, alice) == 0);
3182 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
3183 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3184 }
3185 }
3186
3187 void
3188 testCredential(bool cosigning)
3189 {
3190 testcase("Credential");
3191 using namespace test::jtx;
3192 Account const alice("alice");
3193 Account const bob("bob");
3194 Account const sponsor("sponsor");
3195 Account const sponsor2("sponsor2");
3196 auto const credType = std::string("test");
3197
3198 // Self-issued credential: alice creates for herself, sponsor covers reserve
3199 {
3200 Env env{*this, testableAmendments()};
3201 env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
3202 env.close();
3203
3205 env,
3206 cosigning,
3207 sponsor,
3208 alice,
3209 1,
3210 1,
3212 [&](Env& env, auto const& submit) {
3213 submit(credentials::create(alice, alice, credType));
3214 });
3215
3216 auto const credKeylet = credentials::keylet(alice, alice, credType);
3217 BEAST_EXPECT(env.le(credKeylet)->getAccountID(sfSponsor) == sponsor.id());
3218
3219 BEAST_EXPECT(ownerCount(env, alice) == 1);
3220 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
3221 BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
3222 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
3223
3224 // Transfer sponsor
3225 if (cosigning)
3226 {
3227 env(sponsor::transfer(alice, tfSponsorshipReassign, credKeylet.key),
3228 sponsor::As(sponsor2, spfSponsorReserve),
3229 Sig(sfSponsorSignature, sponsor2));
3230 env.close();
3231 }
3232 else
3233 {
3234 env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice));
3235 env.close();
3236 env(sponsor::transfer(alice, tfSponsorshipReassign, credKeylet.key),
3237 sponsor::As(sponsor2, spfSponsorReserve));
3238 env.close();
3239 }
3240
3241 BEAST_EXPECT(ownerCount(env, alice) == 1);
3242 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
3243 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3244 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
3245 BEAST_EXPECT(env.le(credKeylet)->getAccountID(sfSponsor) == sponsor2.id());
3246
3247 // CredentialDelete
3248 env(credentials::deleteCred(alice, alice, alice, credType));
3249 env.close();
3250
3251 BEAST_EXPECT(ownerCount(env, alice) == 0);
3252 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
3253 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3254 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
3255 }
3256
3257 // Issuer creates for subject; sponsorship transfers to subject on accept
3258 {
3259 Env env{*this, testableAmendments()};
3260 env.fund(XRP(1000000), alice, bob, sponsor);
3261 env.close();
3262
3263 // alice (issuer) creates credential for bob (subject), sponsor covers alice's reserve
3265 env,
3266 cosigning,
3267 sponsor,
3268 alice,
3269 1,
3270 1,
3272 [&](Env& env, auto const& submit) {
3273 submit(credentials::create(bob, alice, credType));
3274 });
3275
3276 // Before accept: alice (issuer) owns the credential
3277 auto const credKeylet = credentials::keylet(bob, alice, credType);
3278 BEAST_EXPECT(env.le(credKeylet)->getAccountID(sfSponsor) == sponsor.id());
3279
3280 BEAST_EXPECT(ownerCount(env, alice) == 1);
3281 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
3282 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
3283 BEAST_EXPECT(ownerCount(env, bob) == 0);
3284 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
3285
3286 // Bob accepts: issuer's sponsorship ends, bob becomes unsponsored owner
3287 env(credentials::accept(bob, alice, credType));
3288 env.close();
3289
3290 BEAST_EXPECT(ownerCount(env, alice) == 0);
3291 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
3292 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); // original sponsorship ended
3293 BEAST_EXPECT(ownerCount(env, bob) == 1);
3294 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0); // bob owns it without a sponsor
3295
3296 // CredentialDelete by subject
3297 env(credentials::deleteCred(bob, bob, alice, credType));
3298 env.close();
3299
3300 BEAST_EXPECT(ownerCount(env, alice) == 0);
3301 BEAST_EXPECT(ownerCount(env, bob) == 0);
3302 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
3303 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
3304 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3305 }
3306
3307 // Issuer creates for subject; accept tx itself is reserve-sponsored
3308 // This exercises the checkReserve() call in CredentialAccept::doApply()
3309 // that guards the sponsor's reserve when featureSponsor is enabled.
3310 {
3311 Env env{*this, testableAmendments()};
3312 env.fund(XRP(1000000), alice, bob, sponsor);
3313 env.close();
3314
3315 // alice (issuer) creates credential for bob (subject) without sponsor
3316 env(credentials::create(bob, alice, credType));
3317 env.close();
3318
3319 auto const credKeylet = credentials::keylet(bob, alice, credType);
3320 BEAST_EXPECT(ownerCount(env, alice) == 1);
3321 BEAST_EXPECT(ownerCount(env, bob) == 0);
3322
3323 // Bob accepts with a sponsored reserve; the first attempt uses an
3324 // undercapitalized sponsor and must fail with tecINSUFFICIENT_RESERVE;
3325 // the second attempt uses a properly funded sponsor and must succeed.
3327 env,
3328 cosigning,
3329 sponsor,
3330 bob,
3331 1,
3332 1,
3334 [&](Env& env, auto const& submit) {
3335 submit(credentials::accept(bob, alice, credType));
3336 });
3337
3338 // After successful accept: alice (issuer) no longer owns the credential,
3339 // bob owns it and sponsor covers his reserve.
3340 BEAST_EXPECT(ownerCount(env, alice) == 0);
3341 BEAST_EXPECT(ownerCount(env, bob) == 1);
3342 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1);
3343 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
3344 BEAST_EXPECT(env.le(credKeylet)->getAccountID(sfSponsor) == sponsor.id());
3345
3346 // Cleanup
3347 env(credentials::deleteCred(bob, bob, alice, credType));
3348 env.close();
3349 }
3350 }
3351
3352 void
3353 testEscrow(bool cosigning)
3354 {
3355 testcase("Escrow");
3356 using namespace test::jtx;
3357 using namespace std::chrono_literals;
3358
3359 Account const alice("alice");
3360 Account const bob("bob");
3361 Account const sponsor("sponsor");
3362 Account const sponsor2("sponsor2");
3363 {
3364 // Native Escrow
3365 Env env{*this, testableAmendments()};
3366 auto const baseFee = env.current()->fees().base;
3367
3368 env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
3369 env.close();
3370
3371 // EscrowCreate
3372 uint32_t seq = 0;
3374 env,
3375 cosigning,
3376 sponsor,
3377 alice,
3378 1,
3379 1,
3381 [&](Env& env, auto const& submit) {
3382 seq = env.seq(alice);
3383 submit(
3384 escrow::create(alice, bob, XRP(100)),
3386 escrow::kCancelTime(env.now() + 100s));
3387 });
3388 BEAST_EXPECT(
3389 env.le(keylet::escrow(alice, SeqProxy::rawSequence(seq)))
3390 ->getAccountID(sfSponsor) == sponsor.id());
3391
3392 // transfer sponsor
3393 if (cosigning)
3394 {
3396 alice,
3397 tfSponsorshipReassign,
3398 keylet::escrow(alice, SeqProxy::rawSequence(seq)).key),
3399 sponsor::As(sponsor2, spfSponsorReserve),
3400 Sig(sfSponsorSignature, sponsor2));
3401 env.close();
3402 }
3403 else
3404 {
3405 env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice));
3406 env.close();
3407
3409 alice,
3410 tfSponsorshipReassign,
3411 keylet::escrow(alice, SeqProxy::rawSequence(seq)).key),
3412 sponsor::As(sponsor2, spfSponsorReserve));
3413 env.close();
3414 }
3415
3416 BEAST_EXPECT(ownerCount(env, alice) == 1);
3417 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
3418 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3419 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
3420
3421 BEAST_EXPECT(
3422 env.le(keylet::escrow(alice, SeqProxy::rawSequence(seq)))
3423 ->getAccountID(sfSponsor) == sponsor2.id());
3424
3425 // EscrowFinish
3426 env(escrow::finish(bob, alice, seq),
3429 Fee(baseFee * 150));
3430 env.close();
3431
3432 BEAST_EXPECT(ownerCount(env, alice) == 0);
3433 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
3434 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3435 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
3436 }
3437
3438 Account const gw("gw");
3439 auto const usd = gw["usd"];
3440 {
3441 // IOU Escrow
3442 Env env{*this, testableAmendments()};
3443 auto const baseFee = env.current()->fees().base;
3444
3445 env.fund(XRP(1000000), alice, bob, gw, sponsor, sponsor2);
3446 env.close();
3447
3448 env(fset(gw, asfAllowTrustLineLocking));
3449 env.close();
3450
3451 env.trust(usd(1000000), alice);
3452 env.close();
3453 env(pay(gw, alice, usd(10000)));
3454 env.close();
3455
3456 BEAST_EXPECT(ownerCount(env, alice) == 1);
3457
3458 // EscrowCreate
3459 uint32_t seq = 0;
3461 env,
3462 cosigning,
3463 sponsor,
3464 alice,
3465 1,
3466 1,
3468 [&](Env& env, auto const& submit) {
3469 seq = env.seq(alice);
3470 submit(
3471 escrow::create(alice, bob, usd(100)),
3473 escrow::kCancelTime(env.now() + 100s));
3474 });
3475
3476 BEAST_EXPECT(
3477 env.le(keylet::escrow(alice, SeqProxy::rawSequence(seq)))
3478 ->getAccountID(sfSponsor) == sponsor.id());
3479
3480 // EscrowFinish
3482 env,
3483 cosigning,
3484 sponsor2,
3485 bob,
3486 1,
3487 1,
3489 [&](Env& env, auto const& submit) {
3490 submit(
3491 escrow::finish(bob, alice, seq),
3494 Fee(baseFee * 150));
3495 });
3496
3497 BEAST_EXPECT(ownerCount(env, alice) == 1);
3498 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
3499 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3500
3501 BEAST_EXPECT(
3502 env.le(keylet::trustLine(bob, gw, usd.currency))->getAccountID(sfHighSponsor) ==
3503 sponsor2.id());
3504 }
3505 {
3506 // IOU EscrowFinish recycles reserve when the same sponsor backs
3507 // the escrow being removed and the destination line being created.
3508 Env env{*this, testableAmendments()};
3509 auto const baseFee = env.current()->fees().base;
3510
3511 env.fund(XRP(1000000), alice, bob, gw, sponsor);
3512 env.close();
3513
3514 env(fset(gw, asfAllowTrustLineLocking));
3515 env.close();
3516
3517 env.trust(usd(1000000), alice);
3518 env.close();
3519 env(pay(gw, alice, usd(10000)));
3520 env.close();
3521
3522 uint32_t seq = 0;
3524 env,
3525 cosigning,
3526 sponsor,
3527 alice,
3528 1,
3529 1,
3531 [&](Env& env, auto const& submit) {
3532 seq = env.seq(alice);
3533 submit(
3534 escrow::create(alice, bob, usd(100)),
3536 escrow::kCancelTime(env.now() + 100s));
3537 });
3538
3539 BEAST_EXPECT(
3540 env.le(keylet::escrow(alice, SeqProxy::rawSequence(seq)))
3541 ->getAccountID(sfSponsor) == sponsor.id());
3542
3543 if (cosigning)
3544 {
3546 }
3547 else
3548 {
3550 env.close();
3551 }
3552
3553 if (cosigning)
3554 {
3555 env(escrow::finish(bob, alice, seq),
3559 Sig(sfSponsorSignature, sponsor),
3560 Fee(baseFee * 150),
3561 Ter(tesSUCCESS));
3562 }
3563 else
3564 {
3565 env(escrow::finish(bob, alice, seq),
3569 Fee(baseFee * 150),
3570 Ter(tesSUCCESS));
3571 }
3572 env.close();
3573
3574 BEAST_EXPECT(ownerCount(env, alice) == 1);
3575 BEAST_EXPECT(ownerCount(env, bob) == 1);
3576 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
3577 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1);
3578 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
3579
3580 BEAST_EXPECT(
3581 env.le(keylet::trustLine(bob, gw, usd.currency))->getAccountID(sfHighSponsor) ==
3582 sponsor.id());
3583 }
3584 {
3585 // IOU Escrow cancel re-creates the owner's trust line, and the
3586 // cancel transaction's sponsor can cover that new line's reserve.
3587 // Creating an IOU escrow moves the sender's balance to the issuer,
3588 // so the sender can delete their now-zero trust line while the
3589 // escrow is pending. Cancelling returns the funds, re-creating the
3590 // line.
3591 Env env{*this, testableAmendments()};
3592
3593 env.fund(XRP(1000000), alice, bob, gw, sponsor, sponsor2);
3594 env.close();
3595
3596 env(fset(gw, asfAllowTrustLineLocking));
3597 env.close();
3598
3599 env.trust(usd(1000000), alice);
3600 env.close();
3601 env(pay(gw, alice, usd(10000)));
3602 env.close();
3603
3604 BEAST_EXPECT(ownerCount(env, alice) == 1);
3605
3606 // alice escrows her entire balance so the trust line can be removed
3607 auto const cancelAfter = env.now() + 100s;
3608 auto const seq = env.seq(alice);
3609 env(escrow::create(alice, bob, usd(10000)),
3611 escrow::kCancelTime(cancelAfter));
3612 env.close();
3613
3614 BEAST_EXPECT(ownerCount(env, alice) == 2); // trust line + escrow
3615
3616 // alice deletes her now-zero trust line while the escrow is pending
3617 env(trust(alice, usd(0)));
3618 env.close();
3619 BEAST_EXPECT(!env.le(keylet::trustLine(alice, gw, usd.currency)));
3620 BEAST_EXPECT(ownerCount(env, alice) == 1); // just the escrow
3621
3622 // advance the ledger past the cancel time
3623 for (; env.now() < cancelAfter; env.close())
3624 {
3625 }
3626
3627 // EscrowCancel by alice re-creates her trust line; the sponsor
3628 // covers the new line's reserve. On the insufficient-reserve
3629 // attempt the whole transaction rolls back, leaving the escrow
3630 // intact to be cancelled on the success attempt.
3631 bool const aliceLow = alice.id() < gw.id();
3633 env,
3634 cosigning,
3635 sponsor,
3636 alice,
3637 1,
3638 1,
3640 [&](Env& env, auto const& submit) { submit(escrow::cancel(alice, alice, seq)); },
3641 [&]() {
3642 BEAST_EXPECT(!env.le(keylet::escrow(alice, SeqProxy::rawSequence(seq))));
3643 auto const trustSle = env.le(keylet::trustLine(alice, gw, usd.currency));
3644 BEAST_EXPECT(trustSle);
3645 if (trustSle)
3646 {
3647 BEAST_EXPECT(
3648 trustSle->getAccountID(aliceLow ? sfLowSponsor : sfHighSponsor) ==
3649 sponsor.id());
3650 BEAST_EXPECT(
3651 !trustSle->isFieldPresent(aliceLow ? sfHighSponsor : sfLowSponsor));
3652 }
3653 BEAST_EXPECT(ownerCount(env, alice) == 1); // re-created trust line
3654 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
3655 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
3656 });
3657 }
3658 {
3659 // MPT Escrow
3660 Env env{*this, testableAmendments()};
3661 env.fund(XRP(1000000), bob, sponsor);
3662 env.close();
3663
3664 MPTTester mptGw(env, gw, {.holders = {alice}});
3665 mptGw.create(
3666 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer});
3667 mptGw.authorize({.account = alice});
3668 auto const mpt = mptGw["MPT"];
3669 env(pay(gw, alice, mpt(10'000)));
3670 env.close();
3671
3672 // create Escrow from alice to bob
3673 auto const seq = env.seq(alice);
3674 env(escrow::create(alice, bob, mpt(100)),
3676 escrow::kCancelTime(env.now() + 100s));
3677 env.close();
3678
3679 BEAST_EXPECT(ownerCount(env, alice) == 2);
3680 BEAST_EXPECT(ownerCount(env, bob) == 0);
3681 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3682
3683 // finish Escrow: bob has no MPToken, so finishing creates one and
3684 // the finish transaction's sponsor covers its reserve. The MPT
3685 // create-token path returns tecINSUFFICIENT_RESERVE (not
3686 // tecNO_LINE_INSUF_RESERVE) when the sponsor is underfunded.
3688 env,
3689 cosigning,
3690 sponsor,
3691 bob,
3692 1,
3693 1,
3695 [&](Env& env, auto const& submit) {
3696 submit(
3697 escrow::finish(bob, alice, seq),
3700 Fee(XRP(1)));
3701 });
3702
3703 BEAST_EXPECT(ownerCount(env, alice) == 1);
3704 BEAST_EXPECT(ownerCount(env, bob) == 1);
3705 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1);
3706 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
3707 BEAST_EXPECT(
3708 env.le(keylet::mptoken(mptGw.issuanceID(), bob))->getAccountID(sfSponsor) ==
3709 sponsor.id());
3710 }
3711 {
3712 // MPT EscrowFinish has the same reserve recycling behavior as IOU
3713 // when it creates the destination MPToken.
3714 Env env{*this, testableAmendments()};
3715 auto const baseFee = env.current()->fees().base;
3716 env.fund(XRP(1000000), bob, sponsor);
3717 env.close();
3718
3719 MPTTester mptGw(env, gw, {.holders = {alice}});
3720 mptGw.create(
3721 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer});
3722 mptGw.authorize({.account = alice});
3723 auto const mpt = mptGw["MPT"];
3724 env(pay(gw, alice, mpt(10'000)));
3725 env.close();
3726
3727 uint32_t seq = 0;
3729 env,
3730 cosigning,
3731 sponsor,
3732 alice,
3733 1,
3734 1,
3736 [&](Env& env, auto const& submit) {
3737 seq = env.seq(alice);
3738 submit(
3739 escrow::create(alice, bob, mpt(100)),
3741 escrow::kCancelTime(env.now() + 100s));
3742 });
3743
3744 BEAST_EXPECT(
3745 env.le(keylet::escrow(alice, SeqProxy::rawSequence(seq)))
3746 ->getAccountID(sfSponsor) == sponsor.id());
3747
3748 if (cosigning)
3749 {
3751 }
3752 else
3753 {
3755 env.close();
3756 }
3757
3758 if (cosigning)
3759 {
3760 env(escrow::finish(bob, alice, seq),
3764 Sig(sfSponsorSignature, sponsor),
3765 Fee(baseFee * 150),
3766 Ter(tesSUCCESS));
3767 }
3768 else
3769 {
3770 env(escrow::finish(bob, alice, seq),
3774 Fee(baseFee * 150),
3775 Ter(tesSUCCESS));
3776 }
3777 env.close();
3778
3779 BEAST_EXPECT(ownerCount(env, alice) == 1);
3780 BEAST_EXPECT(ownerCount(env, bob) == 1);
3781 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
3782 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1);
3783 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
3784
3785 BEAST_EXPECT(
3786 env.le(keylet::mptoken(mptGw.issuanceID(), bob))->getAccountID(sfSponsor) ==
3787 sponsor.id());
3788 }
3789
3790 // A sponsored EscrowCreate must still verify that the source
3791 // can fund the escrow amount and stay above its own base
3792 // reserve. The sponsor covers the new object's owner
3793 // increment, but cannot cover the source's base reserve.
3794 {
3795 Env env{*this, testableAmendments()};
3796 env.fund(XRP(10000), alice, bob, sponsor);
3797 env.close();
3798
3799 // alice's balance is just above the base reserve. After
3800 // locking escrowAmount she would dip below it.
3801 adjustAccountXRPBalance(env, alice, accountReserve(env, 1) + XRP(1));
3802
3803 auto const escrowAmount = XRP(2);
3804 auto const seq = env.seq(alice);
3805
3806 if (cosigning)
3807 {
3808 env(escrow::create(alice, bob, escrowAmount),
3810 escrow::kCancelTime(env.now() + 100s),
3812 Sig(sfSponsorSignature, sponsor),
3813 Ter(tecUNFUNDED));
3814 }
3815 else
3816 {
3817 env(sponsor::set(sponsor, 0, 1, XRP(1)), sponsor::SponseeAcc(alice));
3818 env.close();
3819
3820 env(escrow::create(alice, bob, escrowAmount),
3822 escrow::kCancelTime(env.now() + 100s),
3824 Ter(tecUNFUNDED));
3825 }
3826 env.close();
3827
3828 BEAST_EXPECT(!env.le(keylet::escrow(alice, SeqProxy::rawSequence(seq))));
3829 BEAST_EXPECT(ownerCount(env, alice) == 0);
3830 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
3831 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3832 }
3833 }
3834
3835 void
3836 testMPToken(bool cosigning)
3837 {
3838 testcase("MPToken");
3839 using namespace test::jtx;
3840 Account const alice("alice");
3841 Account const bob("bob");
3842 Account const sponsor("sponsor");
3843 Account const sponsor2("sponsor2");
3844
3845 {
3846 Env env{*this, testableAmendments()};
3847 env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
3848 env.close();
3849
3850 // MPTokenIssuanceCreate
3851 json::Value jv = {};
3852 jv[sfAccount] = alice.human();
3853 jv[sfTransactionType] = jss::MPTokenIssuanceCreate;
3854 MPTID mptid;
3856 env,
3857 cosigning,
3858 sponsor,
3859 alice,
3860 1,
3861 1,
3863 [&](Env& env, auto const& submit) {
3864 mptid = makeMptID(env.seq(alice), alice.id());
3865 submit(jv);
3866 });
3867
3868 // transfer sponsor
3869 auto const mptIssuanceKeylet = keylet::mptokenIssuance(mptid);
3870
3871 if (cosigning)
3872 {
3873 env(sponsor::transfer(alice, tfSponsorshipReassign, mptIssuanceKeylet.key),
3874 sponsor::As(sponsor2, spfSponsorReserve),
3875 Sig(sfSponsorSignature, sponsor2));
3876 env.close();
3877 }
3878 else
3879 {
3880 env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice));
3881 env.close();
3882
3883 env(sponsor::transfer(alice, tfSponsorshipReassign, mptIssuanceKeylet.key),
3884 sponsor::As(sponsor2, spfSponsorReserve));
3885 env.close();
3886 }
3887
3888 // MPTokenAuthorize
3889 jv = {};
3890 jv[sfTransactionType] = jss::MPTokenAuthorize;
3891 jv[sfAccount] = bob.human();
3892 jv[sfMPTokenIssuanceID] = to_string(mptid);
3893
3894 if (cosigning)
3895 {
3897 env(ticket::create(sponsor, 2)); // adjust for free mptoken
3898 env.close();
3899 }
3900
3902 env,
3903 cosigning,
3904 sponsor,
3905 bob,
3906 1,
3907 1,
3909 [&](Env& env, auto const& submit) { submit(jv); });
3910
3911 BEAST_EXPECT(ownerCount(env, alice) == 1);
3912 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
3913
3914 // transfer sponsor
3915 auto const mptTokenKeylet = keylet::mptoken(mptid, bob);
3916 if (cosigning)
3917 {
3918 env(sponsor::transfer(bob, tfSponsorshipReassign, mptTokenKeylet.key),
3919 sponsor::As(sponsor2, spfSponsorReserve),
3920 Sig(sfSponsorSignature, sponsor2));
3921 env.close();
3922 }
3923 else
3924 {
3925 env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(bob));
3926 env.close();
3927
3928 env(sponsor::transfer(bob, tfSponsorshipReassign, mptTokenKeylet.key),
3929 sponsor::As(sponsor2, spfSponsorReserve));
3930 env.close();
3931 }
3932
3933 BEAST_EXPECT(ownerCount(env, alice) == 1);
3934 BEAST_EXPECT(ownerCount(env, bob) == 1);
3935 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
3936 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1);
3937 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3938 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 2);
3939
3940 // MPTokenAuthorize Unauthorize
3941 jv = {};
3942 jv[sfTransactionType] = jss::MPTokenAuthorize;
3943 jv[sfAccount] = bob.human();
3944 jv[sfMPTokenIssuanceID] = to_string(mptid);
3945 jv[sfFlags] = tfMPTUnauthorize;
3946 env(jv);
3947 env.close();
3948
3949 BEAST_EXPECT(ownerCount(env, alice) == 1);
3950 BEAST_EXPECT(ownerCount(env, bob) == 0);
3951 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
3952 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
3953 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
3954
3955 // MPTokenIssuanceDestroy
3956 jv = {};
3957 jv[sfTransactionType] = jss::MPTokenIssuanceDestroy;
3958 jv[sfAccount] = alice.human();
3959 jv[sfMPTokenIssuanceID] = to_string(mptid);
3960 env(jv);
3961 env.close();
3962
3963 BEAST_EXPECT(ownerCount(env, alice) == 0);
3964 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
3965 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
3966 }
3967 {
3968 // check INSUFFICIENT_RESERVE for MPToken
3969 Env env{*this, testableAmendments()};
3970 env.fund(XRP(1000000), alice, bob, sponsor);
3971 env.close();
3972
3973 // MPTokenAuthorize
3974 json::Value jv = {};
3975 jv[sfAccount] = alice.human();
3976 jv[sfTransactionType] = jss::MPTokenIssuanceCreate;
3977 auto const mptid = makeMptID(env.seq(alice), alice.id());
3978 env(jv);
3979 env.close();
3980
3981 // Create tickets so the sponsor is past free-tier reserve behavior.
3982 std::uint32_t const ticketSeq{env.seq(sponsor) + 1};
3983 env(ticket::create(sponsor, 2));
3984 env.close();
3985
3986 jv = {};
3987 jv[sfTransactionType] = jss::MPTokenAuthorize;
3988 jv[sfAccount] = bob.human();
3989 jv[sfMPTokenIssuanceID] = to_string(mptid);
3990 // error (non-free mptoken)
3991 if (cosigning)
3992 {
3993 adjustAccountXRPBalance(env, sponsor, reserve(env, 3) - drops(1));
3994 env(jv,
3996 Sig(sfSponsorSignature, sponsor),
3998 env.close();
3999 }
4000 else
4001 {
4002 env(sponsor::set(sponsor, 0, std::nullopt, XRP(1)), sponsor::SponseeAcc(bob));
4003 env.close();
4004
4006 env.close();
4007 }
4008
4009 env(noop(sponsor), ticket::Use(ticketSeq));
4010 env.close();
4011
4012 // pass (free-tier mptoken for the holder, but the sponsor is still
4013 // charged a reserve increment regardless of the ownerCount < 2 shortcut).
4014 if (cosigning)
4015 {
4017 env(jv,
4019 Sig(sfSponsorSignature, sponsor),
4020 Ter(tesSUCCESS));
4021 env.close();
4022 }
4023 else
4024 {
4026 env.close();
4028 env.close();
4029 }
4030 }
4031 }
4032
4033 void
4034 testPayChan(bool cosigning)
4035 {
4036 testcase("PayChan");
4037 using namespace test::jtx;
4038 using namespace std::literals::chrono_literals;
4039 Account const alice("alice");
4040 Account const bob("bob");
4041 Account const sponsor("sponsor");
4042 Account const sponsor2("sponsor2");
4043
4044 {
4045 Env env{*this, testableAmendments()};
4046 env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
4047 env.close();
4048
4049 // PayChanCreate
4050 auto const pk = alice.pk();
4051 auto const settleDelay = 10s;
4052 uint256 chan;
4054 env,
4055 cosigning,
4056 sponsor,
4057 alice,
4058 1,
4059 1,
4061 [&](Env& env, auto const& submit) {
4062 chan = paychan::channel(alice, bob, env.seq(alice));
4063 submit(paychan::create(alice, bob, XRP(100), settleDelay, pk));
4064 });
4065
4066 // the created PayChannel object carries the sponsor
4067 BEAST_EXPECT(env.le(Keylet(ltPAYCHAN, chan))->getAccountID(sfSponsor) == sponsor.id());
4068
4069 // transfer sponsor
4070 if (cosigning)
4071 {
4072 env(sponsor::transfer(alice, tfSponsorshipReassign, chan),
4073 sponsor::As(sponsor2, spfSponsorReserve),
4074 Sig(sfSponsorSignature, sponsor2));
4075 env.close();
4076 }
4077 else
4078 {
4079 env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice));
4080 env.close();
4081
4082 env(sponsor::transfer(alice, tfSponsorshipReassign, chan),
4083 sponsor::As(sponsor2, spfSponsorReserve));
4084 env.close();
4085 }
4086
4087 BEAST_EXPECT(ownerCount(env, alice) == 1);
4088 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
4089 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
4090 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
4091 BEAST_EXPECT(env.le(Keylet(ltPAYCHAN, chan))->getAccountID(sfSponsor) == sponsor2.id());
4092
4093 env.close(env.now() + settleDelay);
4094 // PayChanClaim (delete PayChan)
4095 env(paychan::claim(bob, chan), Txflags(tfClose));
4096 env.close();
4097
4098 BEAST_EXPECT(ownerCount(env, alice) == 0);
4099 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
4100 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
4101 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
4102 }
4103
4104 // A sponsored PaymentChannelCreate must still verify that the
4105 // source can fund the channel amount and stay above its own
4106 // base reserve. The sponsor covers the new object's owner
4107 // increment, but cannot cover the source's base reserve.
4108 {
4109 Env env{*this, testableAmendments()};
4110 env.fund(XRP(10000), alice, bob, sponsor);
4111 env.close();
4112
4113 // alice's balance is just above the base reserve. After
4114 // locking channelAmount she would dip below it.
4115 adjustAccountXRPBalance(env, alice, accountReserve(env, 1) + XRP(1));
4116
4117 auto const pk = alice.pk();
4118 auto const settleDelay = 10s;
4119 auto const channelAmount = XRP(2);
4120 auto const chan = paychan::channel(alice, bob, env.seq(alice));
4121
4122 if (cosigning)
4123 {
4124 env(paychan::create(alice, bob, channelAmount, settleDelay, pk),
4126 Sig(sfSponsorSignature, sponsor),
4127 Ter(tecUNFUNDED));
4128 }
4129 else
4130 {
4131 env(sponsor::set(sponsor, 0, 1, XRP(1)), sponsor::SponseeAcc(alice));
4132 env.close();
4133
4134 env(paychan::create(alice, bob, channelAmount, settleDelay, pk),
4136 Ter(tecUNFUNDED));
4137 }
4138 env.close();
4139
4140 BEAST_EXPECT(!paychan::channelExists(*env.current(), chan));
4141 BEAST_EXPECT(ownerCount(env, alice) == 0);
4142 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
4143 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
4144 }
4145 }
4146
4147 void
4148 testSignerList(bool cosigning)
4149 {
4150 testcase("SignerList");
4151 using namespace test::jtx;
4152 Account const alice("alice");
4153 Account const bob("bob");
4154 Account const sponsor("sponsor");
4155 Account const sponsor2("sponsor2");
4156
4157 Env env{*this, testableAmendments()};
4158 env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
4159 env.close();
4160
4161 // SignerListSet
4163 env,
4164 cosigning,
4165 sponsor,
4166 alice,
4167 1,
4168 1,
4170 [&](Env& env, auto const& submit) { submit(signers(alice, 1, {{bob, 1}})); });
4171
4172 // the created SignerList object carries the sponsor
4173 BEAST_EXPECT(env.le(keylet::signerList(alice))->getAccountID(sfSponsor) == sponsor.id());
4174
4175 // transfer sponsor
4176 if (cosigning)
4177 {
4178 // invalid signer list owner 1
4179 // account doesn't have signer list but specified signer list exists
4180 env(sponsor::transfer(bob, tfSponsorshipReassign, keylet::signerList(alice).key),
4181 sponsor::As(sponsor2, spfSponsorReserve),
4182 Sig(sfSponsorSignature, sponsor2),
4184 // invalid signer list owner 2
4185 // account has signer list and specified signer list exists
4186 env(signers(bob, 1, {{alice, 1}}));
4187 env.close();
4188 env(sponsor::transfer(alice, tfSponsorshipReassign, keylet::signerList(bob).key),
4189 sponsor::As(sponsor2, spfSponsorReserve),
4190 Sig(sfSponsorSignature, sponsor2),
4192 env(sponsor::transfer(alice, tfSponsorshipReassign, keylet::signerList(alice).key),
4193 sponsor::As(sponsor2, spfSponsorReserve),
4194 Sig(sfSponsorSignature, sponsor2));
4195 env.close();
4196 }
4197 else
4198 {
4199 env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice));
4200 env.close();
4201 env(sponsor::transfer(alice, tfSponsorshipReassign, keylet::signerList(alice).key),
4202 sponsor::As(sponsor2, spfSponsorReserve));
4203 env.close();
4204 }
4205
4206 BEAST_EXPECT(ownerCount(env, alice) == 1);
4207 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
4208 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
4209 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
4210 BEAST_EXPECT(env.le(keylet::signerList(alice))->getAccountID(sfSponsor) == sponsor2.id());
4211
4212 // Delete
4213 env(signers(alice, NoneT()));
4214 env.close();
4215
4216 BEAST_EXPECT(ownerCount(env, alice) == 0);
4217 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
4218 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
4219 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
4220 }
4221
4222 // Legacy (pre-MultiSignReserve) SignerLists lack lsfOneOwnerCount and cost
4223 // 2 + signer_count owner units, whereas modern lists cost 1.
4224 void
4226 {
4227 testcase("Legacy SignerList sponsorship reserve");
4228 using namespace test::jtx;
4229
4230 Account const alice("alice");
4231 Account const bob("bob");
4232 Account const carol("carol");
4233 Account const dave("dave");
4234 Account const sponsor("sponsor");
4235
4236 Env env{*this, testableAmendments()};
4237 env.fund(XRP(1000000), alice, bob, carol, dave, sponsor);
4238 env.close();
4239
4240 // Modern 3-signer list: weight 1, lsfOneOwnerCount set.
4241 env(signers(alice, 1, {{bob, 1}, {carol, 1}, {dave, 1}}));
4242 env.close();
4243
4244 auto const signerListKeylet = keylet::signerList(alice.id());
4245 auto const sponsorKeylet = keylet::sponsorship(sponsor.id(), alice.id());
4246 std::uint32_t const legacyWeight = 5; // 2 + 3 signers
4247 BEAST_EXPECT(ownerCount(env, alice) == 1);
4248
4249 // Pre-fund exactly the legacy weight
4250 env(sponsor::set_reserve(sponsor, 0, legacyWeight), sponsor::SponseeAcc(alice));
4251 env.close();
4252 if (auto const sle = env.le(sponsorKeylet); BEAST_EXPECT(sle))
4253 BEAST_EXPECT(sle->getFieldU32(sfRemainingOwnerCount) == legacyWeight);
4254
4255 // Synthesize a pre-MultiSignReserve list: clear lsfOneOwnerCount and
4256 // restore the owner's OwnerCount to the legacy weight.
4257 env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal) -> bool {
4258 auto signerList = std::make_shared<SLE>(*view.read(signerListKeylet));
4259 auto account = std::make_shared<SLE>(*view.read(keylet::account(alice.id())));
4260 signerList->clearFlag(lsfOneOwnerCount);
4261 account->setFieldU32(sfOwnerCount, legacyWeight);
4262 view.rawReplace(signerList);
4263 view.rawReplace(account);
4264 return true;
4265 });
4266 if (auto const sle = env.le(signerListKeylet); BEAST_EXPECT(sle))
4267 BEAST_EXPECT((sle->getFlags() & lsfOneOwnerCount) == 0);
4268 BEAST_EXPECT(ownerCount(env, alice) == legacyWeight);
4269
4270 // Create must charge the full legacy weight (5), not 1: the bug bumped
4271 // the counters by 1 and left 4 pre-funded units unspent.
4272 env(sponsor::transfer(alice, tfSponsorshipCreate, signerListKeylet.key),
4274
4275 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == legacyWeight);
4276 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == legacyWeight);
4277 // All pre-funded units consumed (drained to absent).
4278 if (auto const sle = env.le(sponsorKeylet); BEAST_EXPECT(sle))
4279 BEAST_EXPECT(!sle->isFieldPresent(sfRemainingOwnerCount));
4280
4281 // Delete unwinds the legacy weight; create bumped by the same amount, so
4282 // the counters return to 0 (the bug bumped by 1 -> underflow on delete).
4283 env(signers(alice, NoneT()));
4284
4285 BEAST_EXPECT(!env.le(signerListKeylet));
4286 BEAST_EXPECT(ownerCount(env, alice) == 0);
4287 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
4288 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
4289 if (auto const sle = env.le(sponsorKeylet); BEAST_EXPECT(sle))
4290 BEAST_EXPECT(!sle->isFieldPresent(sfRemainingOwnerCount));
4291 }
4292
4293 void
4295 {
4296 // An account with ownerCount < 2 may create its first trust lines even
4297 // without meeting the reserve. In any case, the sponsor pays the full
4298 // reserve in all cases, even for the sponsee's very first trust line.
4299 testcase("Sponsored trust line gets no free-reserve exception");
4300 using namespace test::jtx;
4301
4302 Account const issuer("issuer");
4303 Account const alice("alice");
4304 Account const sponsor("sponsor");
4305
4306 Env env{*this, testableAmendments()};
4307 env.fund(XRP(10000), issuer, alice, sponsor);
4308 env.close();
4309
4310 auto const usd = issuer["usd"];
4311 auto const lineKeylet = keylet::trustLine(alice, issuer, usd.currency);
4312
4313 // Sponsor funded for exactly its base reserve
4315
4316 // alice's ownerCount is 0, so an unsponsored first trust line would be
4317 // free; but because it is sponsored, the reserve check is enforced
4318 // against the sponsor, which is one increment short.
4319 env(trust(alice, usd(100)),
4321 Sig(sfSponsorSignature, sponsor),
4323 env.close();
4324
4325 BEAST_EXPECT(!env.le(lineKeylet));
4326 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
4327
4328 // Give the sponsor has exactly one owner-reserve increment; the same
4329 // sponsored first trust line now succeeds and the sponsor pays for it.
4331
4332 env(trust(alice, usd(100)),
4334 Sig(sfSponsorSignature, sponsor));
4335 env.close();
4336
4337 BEAST_EXPECT(env.le(lineKeylet));
4338 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
4339 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
4340 BEAST_EXPECT(ownerCount(env, alice) == 1);
4341 }
4342
4343 void
4345 {
4346 // sponsor co-signs, so a fee-only object (ReserveCount == 0) makes a co-signed
4347 // reserve sponsorship fail -- with no fallback to the sponsor's balance.
4348 testcase("Co-signed reserve sponsorship is bounded by Sponsorship budget");
4349 using namespace test::jtx;
4350
4351 Env env{*this, testableAmendments()};
4352 Account const sponsor("sponsor");
4353 Account const sponsee("sponsee");
4354 env.fund(XRP(10000), sponsor, sponsee);
4355 env.close();
4356
4357 // Prefund a FEE-only Sponsorship for the sponsee; ReserveCount
4358 // defaults to 0.
4359 env(sponsor::set_fee(sponsor, 0, XRP(100)), sponsor::SponseeAcc(sponsee));
4360 env.close();
4361 BEAST_EXPECT(env.le(keylet::sponsorship(sponsor, sponsee)));
4362
4363 // Sponsee creates a Check with the sponsor co-signing the reserve. The
4364 // fee-only Sponsorship's has ReserveCount (0), so this fails
4365 // with tecINSUFFICIENT_RESERVE
4366 env(check::create(sponsee, sponsor, XRP(1)),
4368 Sig(sfSponsorSignature, sponsor),
4370 env.close();
4371
4372 BEAST_EXPECT(ownerCount(env, sponsee) == 0);
4373 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
4374 BEAST_EXPECT(sponsoredOwnerCount(env, sponsee) == 0);
4375
4376 // Bumping the Sponsorship's ReserveCount budget makes the same
4377 // co-signed reserve sponsorship succeed, the budget is what gates it.
4378 env(sponsor::set_reserve(sponsor, 0, 1), sponsor::SponseeAcc(sponsee));
4379 env.close();
4380
4381 env(check::create(sponsee, sponsor, XRP(1)),
4383 Sig(sfSponsorSignature, sponsor),
4384 Ter(tesSUCCESS));
4385 env.close();
4386
4387 BEAST_EXPECT(ownerCount(env, sponsee) == 1);
4388 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
4389 BEAST_EXPECT(sponsoredOwnerCount(env, sponsee) == 1);
4390 }
4391
4392 void
4393 testTrustSet(bool cosigning)
4394 {
4395 testcase("TrustSet");
4396 using namespace test::jtx;
4397 Account const alice("alice");
4398 Account const bob("bob");
4399 Account const charlie("charlie");
4400 Account const sponsor("sponsor");
4401 Account const sponsor2("sponsor2");
4402
4403 auto const validateSponsoredTrustline =
4404 [&](std::shared_ptr<const SLE> const& sle, bool isIssuerHigh, Account const& sponsor) {
4405 BEAST_EXPECT(
4406 sle->getAccountID(isIssuerHigh ? sfLowSponsor : sfHighSponsor) == sponsor.id());
4407 BEAST_EXPECT(!sle->isFieldPresent(isIssuerHigh ? sfHighSponsor : sfLowSponsor));
4408 };
4409
4410 auto const& highAcc = alice > bob ? alice : bob;
4411 auto const& lowAcc = alice > bob ? bob : alice;
4412
4413 // create and delete
4414 for (bool const isIssuerHigh : {false, true})
4415 {
4416 Env env{*this, testableAmendments()};
4417 env.fund(XRP(1000000), alice, bob, charlie, sponsor, sponsor2);
4418 env.close();
4419
4420 auto const& issuer = isIssuerHigh ? highAcc : lowAcc;
4421 auto const& user = isIssuerHigh ? lowAcc : highAcc;
4422
4423 auto const usd = issuer["usd"];
4424 auto const currency = usd.currency;
4425
4426 // create TrustLine
4427 if (cosigning)
4428 {
4430 env(ticket::create(sponsor, 2)); // adjust for free trustline
4431 env.close();
4432 }
4433
4435 env,
4436 cosigning,
4437 sponsor,
4438 user,
4439 1,
4440 1,
4442 [&](Env& env, auto const& submit) { submit(trust(user, usd(100))); });
4443
4444 auto const keylet = keylet::trustLine(user, issuer, currency);
4445
4446 if (cosigning)
4447 {
4448 // invalid owner
4449 env(sponsor::transfer(charlie, tfSponsorshipReassign, keylet.key),
4450 sponsor::As(sponsor2, spfSponsorReserve),
4451 Sig(sfSponsorSignature, sponsor2),
4453 // invalid reserve owner
4454 env(sponsor::transfer(issuer, tfSponsorshipReassign, keylet.key),
4455 sponsor::As(sponsor2, spfSponsorReserve),
4456 Sig(sfSponsorSignature, sponsor2),
4458 env(sponsor::transfer(user, tfSponsorshipReassign, keylet.key),
4459 sponsor::As(sponsor2, spfSponsorReserve),
4460 Sig(sfSponsorSignature, sponsor2));
4461 env.close();
4462 }
4463 else
4464 {
4465 env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(user));
4466 env.close();
4467 env(sponsor::transfer(user, tfSponsorshipReassign, keylet.key),
4468 sponsor::As(sponsor2, spfSponsorReserve));
4469 env.close();
4470 }
4471
4472 // delete TrustLine
4473 env(trust(user, usd(0)));
4474 env.close();
4475
4476 BEAST_EXPECT(ownerCount(env, user) == 0);
4477 BEAST_EXPECT(sponsoredOwnerCount(env, user) == 0);
4478 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
4479
4480 BEAST_EXPECT(!env.le(keylet));
4481 }
4482
4483 // update
4484 for (bool const isIssuerHigh : {false, true})
4485 {
4486 Env env{*this, testableAmendments()};
4487 env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
4488 env.close();
4489
4490 auto const& issuer = isIssuerHigh ? highAcc : lowAcc;
4491 auto const& user = isIssuerHigh ? lowAcc : highAcc;
4492
4493 auto const usd = issuer["usd"];
4494 auto const currency = usd.currency;
4495
4496 // create TrustLine from issuer
4497 env(trust(issuer, user["usd"](100)));
4498 env.close();
4499
4500 BEAST_EXPECT(env.le(keylet::trustLine(user, issuer, currency)));
4501
4502 if (cosigning)
4503 {
4505 env(ticket::create(sponsor, 2)); // adjust for free trustline
4506 env.close();
4507 }
4508
4510 env,
4511 cosigning,
4512 sponsor,
4513 user,
4514 1,
4515 1,
4517 [&](Env& env, auto const& submit) { submit(trust(user, usd(100))); });
4518
4519 auto const line = env.le(keylet::trustLine(user, issuer, currency));
4520 validateSponsoredTrustline(line, isIssuerHigh, sponsor);
4521
4522 // update TrustLine from user to clear reserve
4523 env(trust(user, usd(0)));
4524 env.close();
4525
4526 BEAST_EXPECT(ownerCount(env, user) == 0);
4527 BEAST_EXPECT(sponsoredOwnerCount(env, user) == 0);
4528 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
4529 BEAST_EXPECT(env.le(keylet::trustLine(user, issuer, currency)));
4530
4531 // remove TrustLine from issuer
4532 env(trust(issuer, user["usd"](0)));
4533 env.close();
4534 BEAST_EXPECT(!env.le(keylet::trustLine(user, issuer, currency)));
4535 }
4536
4537 // both High and Low sponsored
4538 {
4539 Env env{*this, testableAmendments()};
4540 env.fund(XRP(1000000), alice, bob, sponsor);
4541 env.close();
4542
4543 // create TrustLines
4544 env(trust(alice, bob["usd"](100)),
4546 Sig(sfSponsorSignature, sponsor));
4547 env.close();
4548 env(trust(bob, alice["usd"](100)),
4550 Sig(sfSponsorSignature, sponsor));
4551 env.close();
4552
4553 auto sle = env.le(keylet::trustLine(alice, bob, alice["usd"].currency));
4554 BEAST_EXPECT(sle);
4555 BEAST_EXPECT(sle->isFlag(lsfHighReserve));
4556 BEAST_EXPECT(sle->isFlag(lsfLowReserve));
4557 BEAST_EXPECT(sle->getAccountID(sfHighSponsor) == sponsor.id());
4558 BEAST_EXPECT(sle->getAccountID(sfLowSponsor) == sponsor.id());
4559
4560 BEAST_EXPECT(ownerCount(env, alice) == 1);
4561 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
4562 BEAST_EXPECT(ownerCount(env, bob) == 1);
4563 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1);
4564 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 2);
4565
4566 // clear TrustLines
4567 env(trust(alice, bob["usd"](0)));
4568 env.close();
4569 env(trust(bob, alice["usd"](0)));
4570 env.close();
4571
4572 sle = env.le(keylet::trustLine(alice, bob, alice["usd"].currency));
4573 BEAST_EXPECT(!sle);
4574 BEAST_EXPECT(ownerCount(env, alice) == 0);
4575 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
4576 BEAST_EXPECT(ownerCount(env, bob) == 0);
4577 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
4578 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
4579 }
4580 }
4581
4582 void
4584 {
4585 testcase("AccountDelete");
4586 using namespace test::jtx;
4587 Account const alice("alice");
4588 Account const bob("bob");
4589 Account const sponsor("sponsor");
4590
4591 {
4592 // Delete Sponsor/Sponsee Account with ltSponsorship (tecHAS_OBLIGATIONS)
4593 Env env{*this, testableAmendments()};
4594 env.fund(XRP(1000000), alice, bob, sponsor);
4595 env.close();
4596
4597 // set sponsor
4598 env(sponsor::set(sponsor, 0, 100, XRP(100)),
4599 sponsor::SponseeAcc(alice),
4600 Ter(tesSUCCESS));
4601 env.close();
4602
4604
4605 auto const keylet = keylet::sponsorship(sponsor, alice);
4606 auto const sponsorObj = env.le(keylet);
4607 BEAST_EXPECT(sponsorObj);
4608
4609 // AccountDelete
4610 auto const requiredFee = drops(env.current()->fees().increment);
4611 env(acctdelete(alice, bob), Fee(requiredFee), Ter(tecHAS_OBLIGATIONS));
4612 env(acctdelete(sponsor, bob), Fee(requiredFee), Ter(tecHAS_OBLIGATIONS));
4613 }
4614
4615 {
4616 // Delete SponsoredAccount
4617 Env env{*this, testableAmendments()};
4618 env.memoize(alice);
4619 env.fund(XRP(1000000), bob, sponsor);
4620 env.close();
4621
4622 // create SponsoredAccount
4623 env(pay(sponsor, alice, XRP(10000)), Txflags(tfSponsorCreatedAccount));
4624 env.close();
4625
4626 incLgrSeqForAccDel(env, alice);
4627
4628 // AccountDelete: destination = non-sponsor
4629 auto const requiredFee = drops(env.current()->fees().increment);
4630 env(acctdelete(alice, bob), Fee(requiredFee), Ter(tecNO_SPONSOR_PERMISSION));
4631
4632 // The failed delete must leave the account sponsored by the original sponsor.
4633 auto const aliceSle = env.le(keylet::account(alice));
4634 BEAST_EXPECT(aliceSle);
4635 if (aliceSle)
4636 BEAST_EXPECT(aliceSle->getAccountID(sfSponsor) == sponsor.id());
4637
4638 auto const sponsorSle = env.le(keylet::account(sponsor));
4639 BEAST_EXPECT(sponsorSle->getFieldU32(sfSponsoringAccountCount) == 1);
4640
4641 incLgrSeqForAccDel(env, alice);
4642
4643 // AccountDelete: destination = sponsor
4644 env(acctdelete(alice, sponsor), Fee(requiredFee), Ter(tesSUCCESS));
4645
4646 auto const sponsorSle2 = env.le(keylet::account(sponsor));
4647 BEAST_EXPECT(!sponsorSle2->isFieldPresent(sfSponsoringAccountCount));
4648 }
4649
4650 {
4651 // Sponsor with sfSponsoringOwnerCount cannot delete (tecHAS_OBLIGATIONS)
4652 Env env{*this, testableAmendments()};
4653 Account const gw("gw");
4654 env.fund(XRP(1000000), alice, bob, sponsor, gw);
4655 env.close();
4656
4657 auto const usd = gw["usd"];
4658
4659 // Create a trust line for alice
4660 env(trust(alice, usd(1000)));
4661 env.close();
4662
4663 // Transfer reserve sponsorship of trust line to sponsor
4664 auto const trustId = keylet::trustLine(alice, gw, usd.currency);
4665 BEAST_EXPECT(env.le(trustId));
4666
4667 env(sponsor::transfer(alice, tfSponsorshipCreate, trustId.key),
4669 Sig(sfSponsorSignature, sponsor));
4670 env.close();
4671
4672 // Verify sfSponsoringOwnerCount is set on sponsor
4673 auto const sponsorSle = env.le(keylet::account(sponsor));
4674 BEAST_EXPECT(sponsorSle->isFieldPresent(sfSponsoringOwnerCount));
4675 auto const sponsoringOwnerCount = sponsorSle->getFieldU32(sfSponsoringOwnerCount);
4676 BEAST_EXPECT(sponsoringOwnerCount >= 1);
4677
4679
4680 // AccountDelete should fail
4681 auto const requiredFee = drops(env.current()->fees().increment);
4682 env(acctdelete(sponsor, bob), Fee(requiredFee), Ter(tecHAS_OBLIGATIONS));
4683 // The failed delete must not decrement the outstanding sponsored-object count.
4684 auto const sponsorSleAfter = env.le(keylet::account(sponsor));
4685 BEAST_EXPECT(sponsorSleAfter->isFieldPresent(sfSponsoringOwnerCount));
4686 BEAST_EXPECT(
4687 sponsorSleAfter->getFieldU32(sfSponsoringOwnerCount) == sponsoringOwnerCount);
4688 }
4689
4690 {
4691 // Sponsor with sfSponsoringAccountCount cannot delete (tecHAS_OBLIGATIONS)
4692 Env env{*this, testableAmendments()};
4693 env.memoize(alice);
4694 env.fund(XRP(1000000), bob, sponsor);
4695 env.close();
4696
4697 // Create SponsoredAccount (sets sfSponsoringAccountCount on sponsor)
4698 env(pay(sponsor, alice, XRP(10000)), Txflags(tfSponsorCreatedAccount));
4699 env.close();
4700
4701 // Verify sfSponsoringAccountCount is set on sponsor
4702 auto const sponsorSle = env.le(keylet::account(sponsor));
4703 BEAST_EXPECT(sponsorSle->isFieldPresent(sfSponsoringAccountCount));
4704 auto const sponsoringAccountCount = sponsorSle->getFieldU32(sfSponsoringAccountCount);
4705 BEAST_EXPECT(sponsoringAccountCount == 1);
4706
4708
4709 // AccountDelete should fail
4710 auto const requiredFee = drops(env.current()->fees().increment);
4711 env(acctdelete(sponsor, bob), Fee(requiredFee), Ter(tecHAS_OBLIGATIONS));
4712 // The failed delete must not decrement the outstanding sponsored-account count.
4713 auto const sponsorSleAfter = env.le(keylet::account(sponsor));
4714 BEAST_EXPECT(sponsorSleAfter->isFieldPresent(sfSponsoringAccountCount));
4715 BEAST_EXPECT(
4716 sponsorSleAfter->getFieldU32(sfSponsoringAccountCount) == sponsoringAccountCount);
4717 }
4718
4719 {
4720 // Account with sponsored objects should be deletable
4721 Env env{*this, testableAmendments()};
4722 env.fund(XRP(1000000), alice, bob, sponsor);
4723 env.close();
4724
4725 // Create sponsored delegate (a non-deletion-blocker)
4726 env(deposit::auth(alice, bob),
4728 Sig(sfSponsorSignature, sponsor));
4729
4730 {
4731 auto const sponsorSle = env.le(keylet::account(sponsor));
4732 auto const aliceSle = env.le(keylet::account(alice));
4733 BEAST_EXPECT(sponsorSle->at(sfSponsoringOwnerCount) == 1);
4734 BEAST_EXPECT(aliceSle->at(sfOwnerCount) == 1);
4735 BEAST_EXPECT(aliceSle->at(sfSponsoredOwnerCount) == 1);
4736 }
4737
4738 incLgrSeqForAccDel(env, alice);
4739
4740 // AccountDelete should succeed
4741 {
4742 auto const requiredFee = drops(env.current()->fees().increment);
4743 env(acctdelete(alice, bob), Fee(requiredFee), Ter(tesSUCCESS));
4744 BEAST_EXPECT(!env.le(keylet::account(alice)));
4745 auto const sponsorSle = env.le(keylet::account(sponsor));
4746 BEAST_EXPECT(sponsorSle->at(sfSponsoringOwnerCount) == 0);
4747 }
4748 }
4749 }
4750
4751 void
4753 {
4754 testcase("DelegatePermission");
4755 using namespace test::jtx;
4756 Account const alice("alice");
4757 Account const bob("bob");
4758 Account const carol("carol");
4759
4760 //
4761 // SponsorshipTransfer is not delegable.
4762 //
4763 {
4764 Env env{*this, testableAmendments()};
4765 env.fund(XRP(1000000), alice, bob, carol);
4766 env.close();
4767
4768 auto const seq = env.seq(alice);
4769 env(check::create(alice, bob, XRP(1)));
4770 env.close();
4771
4772 auto const keylet = keylet::check(alice, SeqProxy::rawSequence(seq));
4773
4774 env(sponsor::transfer(alice, tfSponsorshipCreate, keylet.key),
4776 Sig(sfSponsorSignature, bob),
4777 delegate::As(carol),
4778 Ter(temINVALID));
4779
4780 env(delegate::set(alice, carol, {"SponsorshipTransfer"}), Ter(temMALFORMED));
4781 }
4782 //
4783 // test send SponsorshipSet on other's behalf
4784 //
4785 {
4786 Env env{*this, testableAmendments()};
4787 env.fund(XRP(1000000), alice, bob, carol);
4788 env.close();
4789
4790 env(sponsor::set(alice, 0, 100, XRP(100)),
4792 delegate::As(carol),
4794
4795 env(delegate::set(alice, carol, {"SponsorshipSet"}));
4796 env.close();
4797
4798 env(sponsor::set(alice, 0, 100, XRP(100)),
4800 delegate::As(carol),
4801 Ter(tesSUCCESS));
4802 env.close();
4803 }
4804 }
4805
4806 void
4808 {
4809 testcase("Delegate Block Reserve Sponsor");
4810 using namespace test::jtx;
4811 Account const alice("alice");
4812 Account const bob("bob");
4813 Account const carol("carol");
4814 Account const sponsor("sponsor");
4815
4816 // Co-signed reserve sponsorship is blocked for delegated transactions.
4817 {
4818 Env env{*this, testableAmendments()};
4819 env.fund(XRP(1000000), alice, bob, carol, sponsor);
4820 env.close();
4821
4822 env(delegate::set(alice, bob, {"CheckCreate"}));
4823 env.close();
4824
4825 env(check::create(alice, carol, XRP(1)),
4826 delegate::As(bob),
4828 Sig(sfSponsorSignature, sponsor),
4829 Ter(temINVALID));
4830 }
4831
4832 // Pre-funded reserve sponsorship is blocked for delegated transactions.
4833 {
4834 Env env{*this, testableAmendments()};
4835 env.fund(XRP(1000000), alice, bob, carol, sponsor);
4836 env.close();
4837
4838 env(delegate::set(alice, bob, {"CheckCreate"}));
4840 env.close();
4841
4842 env(check::create(alice, carol, XRP(1)),
4843 delegate::As(bob),
4845 Ter(temINVALID));
4846 }
4847 }
4848
4849 void
4851 {
4852 testcase("Delegate Sponsor Fee Payer");
4853 using namespace test::jtx;
4854 Account const alice("alice");
4855 Account const bob("bob");
4856 Account const carol("carol");
4857 Account const sponsor("sponsor");
4858
4859 // Co-signing: the sponsor account pays the delegated transaction fee.
4860 {
4861 Env env{*this, testableAmendments()};
4862 env.fund(XRP(10000), alice, bob, carol, sponsor);
4863 env.close();
4864
4865 env(delegate::set(alice, bob, {"Payment"}));
4866 env.close();
4867
4868 auto const aliceBalance = env.balance(alice);
4869 auto const bobBalance = env.balance(bob);
4870 auto const carolBalance = env.balance(carol);
4871 auto const sponsorBalance = env.balance(sponsor);
4872 auto const sendAmt = XRP(100);
4873 auto const feeAmt = XRP(10);
4874
4875 env(pay(alice, carol, sendAmt),
4876 delegate::As(bob),
4877 Fee(feeAmt),
4879 Sig(sfSponsorSignature, sponsor));
4880 env.close();
4881
4882 BEAST_EXPECT(env.balance(alice) == aliceBalance - sendAmt);
4883 BEAST_EXPECT(env.balance(bob) == bobBalance);
4884 BEAST_EXPECT(env.balance(carol) == carolBalance + sendAmt);
4885 // sponsor pays the fee
4886 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance - feeAmt);
4887 }
4888
4889 // Pre-funded: the Sponsorship object for sponsorship(sponsor, delegate)
4890 // pays the fee.
4891 {
4892 Env env{*this, testableAmendments()};
4893 env.fund(XRP(10000), alice, bob, carol, sponsor);
4894 env.close();
4895
4896 env(delegate::set(alice, bob, {"Payment"}));
4897 env(sponsor::set_fee(sponsor, 0, XRP(100)), sponsor::SponseeAcc(bob));
4898 env.close();
4899
4900 auto const aliceBalance = env.balance(alice);
4901 auto const bobBalance = env.balance(bob);
4902 auto const carolBalance = env.balance(carol);
4903 auto const sponsorBalance = env.balance(sponsor);
4904 auto const sponsorFee = sponsor::sponsorshipFeeBalance(env, sponsor, bob);
4905 auto const sendAmt = XRP(100);
4906 auto const feeAmt = XRP(10);
4907
4908 // verify sponsorship(sponsor, alice) is not present, because we are testing
4909 // sponsorship(sponsor, bob) will pay the fee. bob is sfDelegate.
4910 BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice)));
4911
4912 env(pay(alice, carol, sendAmt),
4913 delegate::As(bob),
4914 Fee(feeAmt),
4916 env.close();
4917
4918 BEAST_EXPECT(env.balance(alice) == aliceBalance - sendAmt);
4919 BEAST_EXPECT(env.balance(bob) == bobBalance);
4920 BEAST_EXPECT(env.balance(carol) == carolBalance + sendAmt);
4921 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
4922 // sponsorship(sponsor, bob) pays the fee, bob is sfDelegate
4923 BEAST_EXPECT(sponsor::sponsorshipFeeBalance(env, sponsor, bob) == sponsorFee - feeAmt);
4924 }
4925 }
4926
4927 void
4929 {
4930 testcase("Batch");
4931 using namespace test::jtx;
4932 Account const alice("alice");
4933 Account const bob("bob");
4934 Account const sponsor("sponsor");
4935
4936 //
4937 // outer transaction
4938 //
4939 {
4940 // test outer transaction with co-signing sponsor
4941 Env env{*this, testableAmendments()};
4942 env.fund(XRP(1000), alice, bob, sponsor);
4943 env.close();
4944
4945 auto const seq = env.seq(alice);
4946 env(batch::outer(alice, seq, XRP(1), tfAllOrNothing),
4947 batch::Inner(noop(alice), seq + 1),
4948 batch::Inner(ticket::create(alice, 1), seq + 2),
4950 Sig(sfSponsorSignature, sponsor),
4951 Ter(tesSUCCESS));
4952 env.close();
4953
4954 // does not affect reserve
4955 BEAST_EXPECT(ownerCount(env, alice) == 1);
4956 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
4957 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
4958
4959 // Fee is paid by sponsor
4960 BEAST_EXPECT(env.balance(alice) == XRP(1000));
4961 BEAST_EXPECT(env.balance(sponsor) == XRP(1000 - 1));
4962 }
4963 {
4964 Env env{*this, testableAmendments()};
4965 env.fund(XRP(1000), alice, bob, sponsor);
4966 env.close();
4967
4968 // spfSponsorReserve on outer Batch is rejected
4969 for (auto const flags : {spfSponsorReserve | spfSponsorFee, spfSponsorReserve})
4970 {
4971 auto const seq = env.seq(alice);
4972 env(batch::outer(alice, seq, XRP(1), tfAllOrNothing),
4973 batch::Inner(noop(alice), seq + 1),
4974 batch::Inner(noop(alice), seq + 2),
4975 sponsor::As(sponsor, flags),
4976 Sig(sfSponsorSignature, sponsor),
4978 env.close();
4979 }
4980 }
4981 {
4982 // test outer transaction with prefunded sponsor
4983 Env env{*this, testableAmendments()};
4984 env.fund(XRP(1000), alice, bob);
4985 env.fund(XRP(1001), sponsor);
4986 env.close();
4987
4988 env(sponsor::set(sponsor, 0, 100, XRP(100)),
4989 sponsor::SponseeAcc(alice),
4990 Fee(XRP(1)),
4991 Ter(tesSUCCESS));
4992 env.close();
4993
4994 auto const seq = env.seq(alice);
4995 env(batch::outer(alice, seq, XRP(1), tfAllOrNothing),
4996 batch::Inner(noop(alice), seq + 1),
4997 batch::Inner(ticket::create(alice, 1), seq + 2),
4999 Ter(tesSUCCESS));
5000 env.close();
5001
5002 // does not affect reserve
5003 BEAST_EXPECT(ownerCount(env, alice) == 1);
5004 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
5005 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
5006
5007 // Fee is paid by sponsor object
5008 BEAST_EXPECT(env.balance(alice) == XRP(1000));
5009 BEAST_EXPECT(env.balance(sponsor) == XRP(900));
5010
5011 auto const sponsorshipSle = env.le(keylet::sponsorship(sponsor, alice));
5012 BEAST_EXPECT(sponsorshipSle);
5013 BEAST_EXPECT(sponsorshipSle->at(sfFeeAmount) == XRP(100 - 1));
5014 BEAST_EXPECT(sponsorshipSle->at(sfRemainingOwnerCount) == 100);
5015 }
5016 //
5017 // Inner transaction
5018 //
5019 {
5020 // test invalid Inner transaction with co-signing sponsor
5021 Account const signerAccount("signer");
5022 Env env{*this, testableAmendments()};
5023 env.fund(XRP(1000), alice, bob, sponsor, signerAccount);
5024 env.close();
5025
5026 env(signers(sponsor, 1, {Signer(signerAccount, 1)}));
5027 env.close();
5028
5029 {
5030 auto jt = env.jtnofill(
5031 noop(alice),
5033 Sig(sfSponsorSignature, sponsor));
5034 jt.jv.removeMember(sfTxnSignature.jsonName);
5035
5036 auto const seq = env.seq(alice);
5037 // should fail because Inner transaction cannot include SponsorSignature with
5038 // TxnSignature
5039 BEAST_EXPECT(jt.jv[sfSponsorSignature.jsonName].isMember(sfTxnSignature.jsonName));
5040 env(batch::outer(alice, seq, XRP(1), tfAllOrNothing),
5041 batch::Inner(jt.jv, seq + 1),
5042 batch::Inner(ticket::create(alice, 1), seq + 2),
5044 }
5045
5046 {
5047 auto jt = env.jtnofill(
5048 noop(alice),
5050 Msig(sfSponsorSignature, sponsor, signerAccount));
5051 jt.jv.removeMember(sfTxnSignature.jsonName);
5052
5053 auto const seq = env.seq(alice);
5054 // should fail because Inner transaction cannot include SponsorSignature with
5055 // Signers
5056 BEAST_EXPECT(jt.jv[sfSponsorSignature.jsonName].isMember(sfSigners.jsonName));
5057 env(batch::outer(alice, seq, XRP(1), tfAllOrNothing),
5058 batch::Inner(jt.jv, seq + 1),
5059 batch::Inner(ticket::create(alice, 1), seq + 2),
5061 }
5062
5063 {
5064 auto jt = env.jtnofill(
5065 noop(alice),
5067 Sig(sfSponsorSignature, sponsor));
5068 jt.jv.removeMember(sfTxnSignature.jsonName);
5069 jt.jv[sfSponsorSignature.jsonName].removeMember(sfTxnSignature.jsonName);
5070 jt.jv[sfSponsorSignature.jsonName][sfSigningPubKey.jsonName] = "";
5071
5072 auto const seq = env.seq(alice);
5073 // should fail because BatchSigners does not have signer for SponsorSignature
5074 env(batch::outer(alice, seq, XRP(1), tfAllOrNothing),
5075 batch::Inner(jt.jv, seq + 1),
5076 batch::Inner(ticket::create(alice, 1), seq + 2),
5078 }
5079 }
5080
5081 {
5082 // test Inner transaction with prefunded sponsor
5083 Env env{*this, testableAmendments()};
5084 env.fund(XRP(1000), alice, bob);
5085 env.fund(XRP(1001), sponsor);
5086 env.close();
5087
5088 env(sponsor::set(sponsor, 0, 100, XRP(100)),
5089 sponsor::SponseeAcc(alice),
5090 Fee(XRP(1)),
5091 Ter(tesSUCCESS));
5092 env.close();
5093 BEAST_EXPECT(env.balance(sponsor) == XRP(900));
5094
5095 auto jt = env.jtnofill(
5097 // remove txn signature since it is filled by env.jtnofill()
5098 jt.jv.removeMember(jss::TxnSignature);
5099
5100 auto const seq = env.seq(alice);
5101 env(batch::outer(alice, seq, XRP(1), tfAllOrNothing),
5102 batch::Inner(noop(alice), seq + 1),
5103 batch::Inner(jt.jv, seq + 2),
5104 Ter(tesSUCCESS));
5105 env.close();
5106
5107 // affect sponsor reserve
5108 BEAST_EXPECT(ownerCount(env, alice) == 1);
5109 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
5110 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
5111
5112 // Fee is paid by outer transaction originator (alice)
5113 BEAST_EXPECT(env.balance(alice) == XRP(999));
5114 BEAST_EXPECT(env.balance(sponsor) == XRP(900));
5115
5116 // reserve count is decreased
5117 auto const sponsorshipSle = env.le(keylet::sponsorship(sponsor, alice));
5118 BEAST_EXPECT(sponsorshipSle);
5119 BEAST_EXPECT(sponsorshipSle->at(sfFeeAmount) == XRP(100));
5120 BEAST_EXPECT(sponsorshipSle->at(sfRemainingOwnerCount) == 99);
5121 }
5122
5123 {
5124 // test Inner transaction with co-signing sponsor
5125 Env env{*this, testableAmendments()};
5126 env.fund(XRP(1000), alice, bob, sponsor);
5127 env.close();
5128
5129 auto jt = env.jtnofill(
5130 check::create(alice, bob, XRP(1)),
5132 Sig(sfSponsorSignature, sponsor));
5133 // remove txn signature since it is filled by env.jtnofill()
5134 jt.jv.removeMember(sfTxnSignature.jsonName);
5135 jt.jv[sfSponsorSignature.jsonName].removeMember(sfTxnSignature.jsonName);
5136 jt.jv[sfSponsorSignature.jsonName][sfSigningPubKey.jsonName] = "";
5137
5138 auto const seq = env.seq(alice);
5139 env(batch::outer(alice, seq, XRP(1), tfAllOrNothing),
5140 batch::Inner(noop(alice), seq + 1),
5141 batch::Inner(jt.jv, seq + 2),
5143 Ter(tesSUCCESS));
5144 env.close();
5145
5146 // affect sponsor reserve
5147 BEAST_EXPECT(ownerCount(env, alice) == 1);
5148 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
5149 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
5150
5151 // Fee is paid by outer transaction originator (alice)
5152 BEAST_EXPECT(env.balance(alice) == XRP(999));
5153 BEAST_EXPECT(env.balance(sponsor) == XRP(1000));
5154 }
5155
5156 // Inner tx with sfSponsor + spfSponsorFee (pre-funded) is rejected
5157 {
5158 Env env{*this, testableAmendments()};
5159 env.fund(XRP(1000), alice, bob, sponsor);
5160 env.close();
5161
5162 // Create pre-funded sponsorship
5163 env(sponsor::set_fee(sponsor, 0, XRP(1)), sponsor::SponseeAcc(alice), Fee(XRP(1)));
5164 env.close();
5165
5166 auto const seq = env.seq(alice);
5167 auto const batchFee = batch::calcBatchFee(env, 0, 2);
5168
5169 // Create inner transaction with sponsor fee
5170 auto innerPay = pay(alice, bob, XRP(1));
5171 innerPay[sfSponsor.jsonName] = sponsor.human();
5172 innerPay[sfSponsorFlags.jsonName] = static_cast<std::uint32_t>(spfSponsorFee);
5173
5174 // Should be rejected with temBAD_FEE
5175 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
5176 batch::Inner(innerPay, seq + 1),
5177 batch::Inner(noop(alice), seq + 2),
5179 env.close();
5180 }
5181
5182 // Inner tx with sfSponsor + spfSponsorFee (co-signed) is rejected
5183 {
5184 Env env{*this, testableAmendments()};
5185 env.fund(XRP(1000), alice, bob, sponsor);
5186 env.close();
5187
5188 auto const seq = env.seq(alice);
5189 auto const batchFee = batch::calcBatchFee(env, 1, 2);
5190
5191 auto innerPay = pay(alice, bob, XRP(1));
5192 innerPay[sfSponsor.jsonName] = sponsor.human();
5193 innerPay[sfSponsorFlags.jsonName] = static_cast<std::uint32_t>(spfSponsorFee);
5194
5195 // Should be rejected with temBAD_FEE
5196 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
5197 batch::Inner(innerPay, seq + 1),
5198 batch::Inner(noop(alice), seq + 2),
5201 env.close();
5202 }
5203
5204 // Inner tx with spfSponsorFee + spfSponsorReserve is rejected
5205 {
5206 Env env{*this, testableAmendments()};
5207 env.fund(XRP(1000), alice, bob, sponsor);
5208 env.close();
5209
5210 auto const seq = env.seq(alice);
5211 auto const batchFee = batch::calcBatchFee(env, 0, 2);
5212
5213 auto innerTx = check::create(alice, bob, XRP(1));
5214 innerTx[sfSponsor.jsonName] = sponsor.human();
5215 innerTx[sfSponsorFlags.jsonName] =
5217
5218 // Should be rejected with temBAD_FEE (fee sponsorship check comes first)
5219 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
5220 batch::Inner(innerTx, seq + 1),
5221 batch::Inner(noop(alice), seq + 2),
5223 env.close();
5224 }
5225
5226 // Outer batch tx with sponsor fee is allowed
5227 {
5228 Env env{*this, testableAmendments()};
5229 env.fund(XRP(1000), alice, bob, sponsor);
5230 env.close();
5231
5232 auto const seq = env.seq(bob);
5233 auto const batchFee = batch::calcBatchFee(env, 0, 2);
5234
5235 // Outer batch with sponsor fee should work fine
5236 env(batch::outer(bob, seq, batchFee, tfAllOrNothing),
5237 batch::Inner(noop(bob), seq + 1),
5238 batch::Inner(noop(bob), seq + 2),
5240 Sig(sfSponsorSignature, sponsor),
5241 Ter(tesSUCCESS));
5242 env.close();
5243
5244 // Sponsor paid the fee
5245 BEAST_EXPECT(env.balance(bob) == XRP(1000));
5246 }
5247 }
5248
5249 // Verify that the central allow-list in preflight1Sponsor rejects
5250 // spfSponsorReserve for transaction types that v1 does not permit.
5251 void
5253 {
5254 testcase("Reserve sponsor allow-list gate");
5255 using namespace test::jtx;
5256
5257 Env env{*this, testableAmendments()};
5258 Account const alice("alice");
5259 Account const bob("bob");
5260 Account const sponsor("sponsor");
5261 env.fund(XRP(10000), alice, bob, sponsor);
5262 env.close();
5263
5264 env(sponsor::set(sponsor, 0, 10, XRP(10)), sponsor::SponseeAcc(alice));
5265 env.close();
5266
5267 auto checkBlocked = [&](json::Value const& jv) {
5268 env(jv,
5270 Sig(sfSponsorSignature, sponsor),
5272 };
5273
5274 checkBlocked(ticket::create(alice, 1));
5275 checkBlocked(offer(alice, XRP(100), bob["USD"](100)));
5276 checkBlocked(did::setValid(alice));
5277 checkBlocked(token::mint(alice, 0u));
5278 checkBlocked(sponsor::set(alice, 0, 10, XRP(10)));
5279 checkBlocked(acctdelete(alice, bob));
5280 checkBlocked(loan::set(alice, uint256(1), Number{1}));
5281 }
5282
5283 void
5284 testSponsorReserve(bool cosigning)
5285 {
5287 testSponsorReserveSimple(cosigning);
5288 testCheck(cosigning);
5289 testCredential(cosigning);
5290 testDelegate(cosigning);
5291 testDepositPreauth(cosigning);
5292 testEscrow(cosigning);
5293 testMPToken(cosigning);
5294 testPayChan(cosigning);
5295 testSignerList(cosigning);
5296 testTrustSet(cosigning);
5297 }
5298
5299 void
5301 {
5302 // Zero-balance sponsored Payment: getFeePayer() consistency check
5303 testcase("Sponsored Payment: minimal-balance account with sponsor-pays-fee");
5304
5305 using namespace jtx;
5306 Env env{*this, testableAmendments()};
5307 Account const alice("alice");
5308 Account const sponsor("sponsor");
5309 Account const dest("dest");
5310
5311 auto const baseFee = env.current()->fees().base;
5312 auto const baseReserve = env.current()->fees().reserve;
5313
5314 // Fund sponsor and dest generously, alice with base reserve + 1 XRP for payment
5315 env.fund(XRP(10000), sponsor, dest);
5316 env.fund(baseReserve + XRP(1), alice);
5317 env.close();
5318
5319 // Precondition: alice has base reserve + 1 XRP (enough for payment but not fee)
5320 BEAST_EXPECT(env.balance(alice) == baseReserve + XRP(1));
5321
5322 // Alice tries to send a Payment to dest where sponsor pays the fee via spfSponsorFee.
5323 // Passed even alice balance doesn't have enough to pay fee.
5324 auto const preDest = env.balance(dest);
5325 auto const preSponsor = env.balance(sponsor);
5326
5327 // Alice sends 1 XRP to dest, sponsor pays the fee
5328 env(pay(alice, dest, XRP(1)),
5330 Sig(sfSponsorSignature, sponsor),
5331 Fee(baseFee));
5332 env.close();
5333
5334 // Payment succeeded
5335 // Alice's balance decreased by 1 XRP (the payment amount, NOT the fee)
5336 BEAST_EXPECT(env.balance(alice) == baseReserve);
5337
5338 // Dest received 1 XRP
5339 BEAST_EXPECT(env.balance(dest) == preDest + XRP(1));
5340
5341 // Sponsor paid the fee (NOT alice)
5342 BEAST_EXPECT(env.balance(sponsor) == preSponsor - baseFee);
5343 }
5344
5345 void
5347 {
5348 // TrustSet's modify path applies the tx-level reserve sponsor to whichever
5349 // side has its reserve gate trip on this update, regardless of whether that
5350 // side belongs to the tx submitter. trustCreate only sets the submitter's
5351 // reserve flag and snapshots the counterparty's asfDefaultRipple state into
5352 // the line's NoRipple bit; if the counterparty later toggles asfDefaultRipple
5353 // (the canonical issuer flow), the line and account flags disagree and on
5354 // the submitter's next TrustSet the counterparty-side gate fires. Sponsor still will be
5355 // checked if it can be applied to that end of the trustLine.
5356
5357 testcase("TrustSet modify with sponsor does not misroute onto counterparty side");
5358
5359 using namespace test::jtx;
5360
5361 Env env{*this, testableAmendments()};
5362 Account const alice{"alice_t2178"};
5363 Account const bob{"bob_t2178"};
5364 Account const carol{"carol_t2178"};
5365
5366 // Fund without auto-setting asfDefaultRipple
5367 env.fund(XRP(100'000), alice, bob, carol);
5368 env.close();
5369
5370 // Determine account ordering
5371 bool const aliceIsHigh = alice.id() > bob.id();
5372
5373 // To trigger the bug, we need the COUNTERPARTY's reserve gate to trip
5374 // We use issuer/holder terminology where:
5375 // - holder creates the trust line (their reserve is set first)
5376 // - issuer enables DefaultRipple after (creates flag mismatch)
5377 // - holder's second TrustSet triggers issuer's reserve gate
5378
5379 auto const issuer = aliceIsHigh ? bob : alice;
5380 auto const holder = aliceIsHigh ? alice : bob;
5381 auto const usd = issuer["USD"];
5382
5383 // Issuer must NOT have DefaultRipple set initially
5384 // Clear it explicitly (env.fund may have set it)
5385 env(fclear(issuer, asfDefaultRipple));
5386 env.close();
5387
5388 // Holder creates the trust line first (holder's reserve flag is set)
5389 // At this point, issuer does NOT have DefaultRipple set, so
5390 // the NoRipple bit on issuer's side is set according to issuer's current flag
5391 env(trust(holder, usd(1'000)));
5392 env.close();
5393
5394 // Issuer now enables asfDefaultRipple (canonical issuer flow)
5395 // This creates a mismatch: issuer's account flag says DefaultRipple=true
5396 // but the trust line's NoRipple bit on issuer's side is still set
5397 env(fset(issuer, asfDefaultRipple));
5398 env.close();
5399
5400 SF_ACCOUNT const& issuerSponsorField = aliceIsHigh ? sfLowSponsor : sfHighSponsor;
5401 SF_ACCOUNT const& holderSponsorField = aliceIsHigh ? sfHighSponsor : sfLowSponsor;
5402
5403 auto const lineKey = keylet::trustLine(alice, bob, usd.currency);
5404 auto const sleLineBefore = env.le(lineKey);
5405 if (!BEAST_EXPECT(sleLineBefore))
5406 return;
5407 BEAST_EXPECT(!sleLineBefore->isFieldPresent(sfLowSponsor));
5408 BEAST_EXPECT(!sleLineBefore->isFieldPresent(sfHighSponsor));
5409
5410 auto const carolBefore = sponsoringOwnerCount(env, carol);
5411 BEAST_EXPECT(carolBefore == 0);
5412 auto const issuerSponsoredBefore = sponsoredOwnerCount(env, issuer);
5413 BEAST_EXPECT(issuerSponsoredBefore == 0);
5414
5415 // Holder modifies the trust line with Carol as sponsor
5416 // This should trigger the issuer's reserve gate because of the DefaultRipple mismatch
5417 // Carol (sponsor) should NOT be applied to issuer's side (issuer != tx submitter)
5418 env(trust(holder, usd(2'000)),
5420 Sig(sfSponsorSignature, carol),
5421 Ter(tesSUCCESS));
5422 env.close();
5423
5424 auto const sleLineAfter = env.le(lineKey);
5425 if (!BEAST_EXPECT(sleLineAfter))
5426 return;
5427
5428 // Carol only agreed to back the holder, not the issuer
5429 BEAST_EXPECT(!sleLineAfter->isFieldPresent(issuerSponsorField));
5430
5431 // Holder's side also has no sponsor because holder's reserve flag was
5432 // already set on the FIRST TrustSet (no sponsor in scope then)
5433 BEAST_EXPECT(!sleLineAfter->isFieldPresent(holderSponsorField));
5434
5435 // Carol's sponsoring count should remain unchanged (no misroute)
5436 auto const carolAfter = sponsoringOwnerCount(env, carol);
5437 BEAST_EXPECT(carolAfter == carolBefore);
5438
5439 // Issuer's sponsored count should remain unchanged (no misroute)
5440 auto const issuerSponsoredAfter = sponsoredOwnerCount(env, issuer);
5441 BEAST_EXPECT(issuerSponsoredAfter == issuerSponsoredBefore);
5442 }
5443
5444 void
5446 {
5447 testcase("Self-escrow finish reserve order gated by amendment");
5448 using namespace test::jtx;
5449 using namespace std::chrono_literals;
5450
5451 // Finishing a self-escrow (source == destination) whose trust line
5452 // was deleted while the escrow was outstanding auto-creates the line,
5453 // and the outcome of that reserve check depends on whether the escrow
5454 // reserve is released before delivery (Sponsor) or after (legacy).
5455 // With the source's balance in the one-increment window
5456 // [reserve(1), reserve(2)), the legacy order requires reserve(2) and
5457 // fails, while the Sponsor order requires reserve(1) and succeeds.
5458 auto runTest = [&](FeatureBitset features, TER expected) {
5459 Account const alice("alice");
5460 Account const gw("gw");
5461 auto const usd = gw["usd"];
5462
5463 Env env{*this, features};
5464 auto const baseFee = env.current()->fees().base;
5465
5466 env.fund(XRP(10000), alice, gw);
5467 env.close();
5468
5469 env(fset(gw, asfAllowTrustLineLocking));
5470 env.close();
5471
5472 env.trust(usd(1000), alice);
5473 env.close();
5474 env(pay(gw, alice, usd(100)));
5475 env.close();
5476
5477 // Escrow alice's entire USD balance to herself. The escrowed
5478 // IOUs return to the issuer, zeroing the line balance.
5479 auto const seq = env.seq(alice);
5480 env(escrow::create(alice, alice, usd(100)),
5482 escrow::kCancelTime(env.now() + 100s));
5483 env.close();
5484
5485 // Delete the now-empty trust line. Both accounts have
5486 // DefaultRipple set (jtx fund does that), so a plain limit-0
5487 // TrustSet returns the line to its default state.
5488 env(trust(alice, usd(0)));
5489 env.close();
5490 BEAST_EXPECT(!env.le(keylet::trustLine(alice, gw, usd.currency)));
5491 BEAST_EXPECT(ownerCount(env, alice) == 1); // just the escrow
5492
5493 // Put alice's balance in the window. Pay the excess away
5494 // directly: adjustAccountXRPBalance needs the Sponsor amendment.
5495 STAmount const target = reserve(env, 1) + XRP(1);
5496 env(pay(alice, env.master, env.balance(alice) - target - baseFee), Fee(baseFee));
5497 env.close();
5498 BEAST_EXPECT(env.balance(alice) == target);
5499
5500 env(escrow::finish(alice, alice, seq),
5503 Fee(baseFee * 150),
5504 Ter(expected));
5505 env.close();
5506
5507 if (expected == tesSUCCESS)
5508 {
5509 BEAST_EXPECT(!env.le(keylet::escrow(alice, SeqProxy::rawSequence(seq))));
5510 BEAST_EXPECT(env.le(keylet::trustLine(alice, gw, usd.currency)));
5511 BEAST_EXPECT(env.balance(alice, usd) == usd(100));
5512 BEAST_EXPECT(ownerCount(env, alice) == 1); // the new line
5513 }
5514 else
5515 {
5516 BEAST_EXPECT(env.le(keylet::escrow(alice, SeqProxy::rawSequence(seq))));
5517 BEAST_EXPECT(!env.le(keylet::trustLine(alice, gw, usd.currency)));
5518 BEAST_EXPECT(ownerCount(env, alice) == 1); // still the escrow
5519 }
5520 };
5521
5522 // Pre-amendment: legacy order — the escrow still counts against the
5523 // reserve while the auto-created line is checked.
5524 runTest(testableAmendments() - featureSponsor, tecNO_LINE_INSUF_RESERVE);
5525
5526 // Post-amendment: the escrow reserve is recycled into the new line.
5527 runTest(testableAmendments(), tesSUCCESS);
5528 }
5529
5530 void
5532 {
5533 // The ValidVault invariant checks that the vault's balance and the
5534 // depositor's balance change by equal amounts. For XRP vaults it adds the
5535 // fee back into the depositor's balance change: normally the depositor
5536 // pays the fee, so their balance drops by (deposit amount + fee), and
5537 // adding the fee back leaves just the deposit amount to compare against
5538 // the vault. But when a fee sponsor pays, the depositor's balance drops
5539 // by only the deposit amount, so the fee must NOT be added back or the
5540 // equal-amount check fails.
5541 testcase("Fee-sponsored VaultDeposit/VaultWithdraw pass ValidVault invariant");
5542 using namespace test::jtx;
5543
5544 Env env{*this, testableAmendments()};
5545 Account const alice("alice");
5546 Account const sponsor("sponsor");
5547 env.fund(XRP(10000), alice, sponsor);
5548 env.close();
5549
5550 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
5551 Vault const vault{env};
5552 auto [vaultTx, vaultKeylet] = vault.create({.owner = alice, .asset = xrpAsset});
5553 env(vaultTx);
5554 env.close();
5555
5556 // Control: the same deposit shape, unsponsored, succeeds.
5557 env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = xrpAsset(100)}),
5558 Ter(tesSUCCESS));
5559 env.close();
5560
5561 // Fee-sponsored (co-signed) deposit succeeds.
5562 env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = xrpAsset(100)}),
5563 Fee(XRP(1)),
5565 Sig(sfSponsorSignature, sponsor),
5566 Ter(tesSUCCESS));
5567 env.close();
5568
5569 // The same helper (deltaAssetsTxAccount) drives the withdraw path, so a
5570 // fee-sponsored withdrawal back to the depositor's own account also
5571 // passes on the destination side.
5572 env(vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = xrpAsset(50)}),
5573 Fee(XRP(1)),
5575 Sig(sfSponsorSignature, sponsor),
5576 Ter(tesSUCCESS));
5577 env.close();
5578 }
5579
5580 void
5582 {
5583 // Deleting a co-signed reserve-sponsored object must
5584 // refund the sponsor's SponsoringOwnerCount back to zero.
5585 testcase("Sponsored object deletion refunds sponsor owner count");
5586 using namespace test::jtx;
5587
5588 Env env{*this, testableAmendments()};
5589 Account const sponsor("sponsor");
5590 env.fund(XRP(100000), sponsor);
5591 env.close();
5592
5593 // Sponsored Check deletion - verify decreaseOwnerCountForObject
5594 // (used by CheckCancel) reads the object's sfSponsor field and refunds
5595 // the sponsor's owner count.
5596 {
5597 testcase(" — Check deletion");
5598
5599 Account const checkOwner("check_owner");
5600 Account const dest("check_dest");
5601 env.fund(XRP(100000), checkOwner, dest);
5602 env.close();
5603
5604 // Create a check with co-signed reserve sponsorship. This bumps the
5605 // sponsor's sfSponsoringOwnerCount rather than the check owner's.
5606 auto const checkSeq = env.seq(checkOwner);
5607 env(check::create(checkOwner, dest, XRP(1)),
5609 Sig(sfSponsorSignature, sponsor));
5610 env.close();
5611
5612 auto sponsorCountBefore = sponsoringOwnerCount(env, sponsor);
5613 BEAST_EXPECT(sponsorCountBefore == 1); // check costs 1 owner count
5614
5615 // Cancel (delete) the check.
5616 env(check::cancel(
5617 checkOwner, keylet::check(checkOwner, SeqProxy::rawSequence(checkSeq)).key));
5618 env.close();
5619
5620 auto sponsorCountAfter = sponsoringOwnerCount(env, sponsor);
5621 BEAST_EXPECT(sponsorCountAfter == 0); // fully refunded
5622 }
5623
5624 // Sponsored TrustSet (trust line) deletion - verify the sponsor
5625 // refund works when a sponsored trust line is deleted
5626 {
5627 testcase(" — TrustLine deletion");
5628
5629 Account const issuer("issuer");
5630 Account const holder("holder");
5631 env.fund(XRP(100000), issuer, holder);
5632 env.close();
5633
5634 auto const usd = issuer["USD"];
5635
5636 // Create trust line with sponsorship on the holder side
5637 env(trust(holder, usd(1000)),
5639 Sig(sfSponsorSignature, sponsor));
5640 env.close();
5641
5642 auto sponsorCountBefore = sponsoringOwnerCount(env, sponsor);
5643 BEAST_EXPECT(sponsorCountBefore == 1); // trust line costs 1
5644
5645 // Delete the trust line by clearing it to default
5646 env(trust(holder, usd(0)));
5647 env.close();
5648
5649 auto sponsorCountAfter = sponsoringOwnerCount(env, sponsor);
5650 BEAST_EXPECT(sponsorCountAfter == 0); // fully refunded
5651 }
5652 }
5653
5654protected:
5655 void
5698
5699 void
5700 testTxSponsor(bool cosigning)
5701 {
5702 testSponsorReserve(cosigning);
5703 }
5704
5705public:
5706 void
5707 run() override
5708 {
5709 testSponsor();
5710 }
5711};
5712
5714{
5715 void
5716 run() override
5717 {
5718 testTxSponsor(true);
5719 }
5720};
5721
5723{
5724 void
5725 run() override
5726 {
5727 testTxSponsor(false);
5728 }
5729};
5730
5732BEAST_DEFINE_TESTSUITE(SponsorTxCosigning, app, xrpl);
5733BEAST_DEFINE_TESTSUITE(SponsorTxPrefunded, app, xrpl);
5734
5735} // namespace xrpl::test
A generic endpoint for log messages.
Definition Journal.h:44
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.
Number is a floating point type that can represent a wide range of values.
Definition Number.h:351
bool modify(modify_type const &f)
Modify the open ledger.
Writable ledger view that accumulates state and tx changes.
Definition OpenView.h:59
SLE::const_pointer read(Keylet const &k) const override
Return the state item associated with a key.
Definition OpenView.cpp:167
Rules const & rules() const override
Returns the tx processing rules.
Definition OpenView.cpp:148
void rawReplace(SLE::ref sle) override
Unconditionally replace a state item.
Definition OpenView.cpp:243
Slice slice() const noexcept
Definition PublicKey.h:115
static constexpr SeqProxy rawSequence(std::uint32_t v)
Factory function to return a sequence-based SeqProxy.
Definition SeqProxy.h:62
static constexpr SeqProxy rawTicket(std::uint32_t v)
Factory function to return a ticket-based SeqProxy.
Definition SeqProxy.h:74
virtual OpenLedger & getOpenLedger()=0
void run() override
Runs the suite.
void run() override
Runs the suite.
void testPayChan(bool cosigning)
void testTxSponsor(bool cosigning)
void testSponsorReserveSimple(bool cosigning)
void run() override
Runs the suite.
void testCredential(bool cosigning)
void testSignerList(bool cosigning)
void testTrustSet(bool cosigning)
void testSponsorReserve(bool cosigning)
void testCheck(bool cosigning)
void testDelegate(bool cosigning)
void testEscrow(bool cosigning)
void testZeroBalanceSponsoredPaymentFeePayerCheck()
void testEachSponsorship(test::jtx::Env &env, bool cosigning, jtx::Account const &sponsor, jtx::Account const &sponsee, uint32_t reserveCount, uint32_t sponsorReserveCount, TER insufficientReserveResult, SubmitCallback callback, std::optional< std::function< void()> > expected=std::nullopt)
void testMPToken(bool cosigning)
void testCoSignReserveBoundedBySponsorshipBudget()
void testDepositPreauth(bool cosigning)
Immutable cryptographic account descriptor.
Definition jtx/Account.h:21
std::string const & human() const
Returns the human readable public key.
PublicKey const & pk() const
Return the public key.
Definition jtx/Account.h:84
AccountID id() const
Returns the Account ID.
A transaction testing environment.
Definition Env.h:161
Application & app()
Definition Env.h:300
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
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition Env.cpp:127
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
Account const & master
Definition Env.h:165
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition Env.h:721
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
JTx jtnofill(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition Env.h:735
void memoize(Account const &account)
Associate AccountID with account.
Definition Env.cpp:174
beast::Journal const journal
Definition Env.h:204
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
Test helper for creating, mutating, and asserting MPT and confidential MPT ledger state.
Definition mpt.h:447
static json::Value authorizeJV(MPTAuthorize const &arg=MPTAuthorize{})
Definition mpt.cpp:338
void create(MPTCreate const &arg=MPTCreate{})
Definition mpt.cpp:241
Set a multisignature on a JTx.
Definition multisign.h:53
Add a path.
Definition paths.h:47
Sets the SendMax on a JTx.
Definition sendmax.h:16
Set the regular signature on a JTx.
Definition sig.h:19
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
Adds an inner Batch transaction to a JTx and autofills it.
Definition batch.h:66
Sets the Batch transaction signers on a JTx.
Definition batch.h:117
Set a ticket sequence on a JTx.
Definition ticket.h:36
T make_shared(T... args)
T max(T... args)
Keylet computation functions.
Definition Indexes.h:40
Keylet const & skip() noexcept
The index of the "short" skip list.
Definition Indexes.cpp:210
Keylet escrow(AccountID const &src, SeqProxy const &seq) noexcept
An escrow entry.
Definition Indexes.cpp:388
Keylet did(AccountID const &account) noexcept
Definition Indexes.cpp:525
Keylet unchecked(uint256 const &key) noexcept
Any ledger entry.
Definition Indexes.cpp:367
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition Indexes.cpp:344
Keylet loan(uint256 const &loanBrokerID, SeqProxy const &loanSeq) noexcept
Definition Indexes.cpp:573
Keylet const & feeSettings() noexcept
The (fixed) index of the object containing the ledger fees.
Definition Indexes.cpp:233
Keylet signerList(AccountID const &account) noexcept
A SignerList.
Definition Indexes.cpp:326
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:373
Keylet check(AccountID const &id, SeqProxy const &seq) noexcept
A Check.
Definition Indexes.cpp:338
Keylet ticket(AccountID const &id, SeqProxy const &ticketSeq)
A ticket belonging to an account.
Definition Indexes.cpp:310
Keylet nftokenPageMax(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition Indexes.cpp:408
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:543
Keylet delegate(AccountID const &account, AccountID const &authorizedAccount) noexcept
A keylet for Delegate object.
Definition Indexes.cpp:481
Keylet loanBroker(AccountID const &owner, SeqProxy const &seq) noexcept
Definition Indexes.cpp:567
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:198
Keylet sponsorship(AccountID const &sponsor, AccountID const &sponsee) noexcept
A Sponsorship.
Definition Indexes.cpp:332
Keylet mptokenIssuance(MPTID const &issuanceID) noexcept
Definition Indexes.cpp:537
Keylet trustLine(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:253
json::Value outer(jtx::Account const &account, uint32_t seq, STAmount const &fee, std::uint32_t flags)
Build an outer Batch transaction JSON object.
Definition batch.cpp:53
XRPAmount calcBatchFee(jtx::Env const &env, uint32_t const &numSigners, uint32_t const &txns=0)
Calculate the expected outer Batch transaction fee.
Definition batch.cpp:35
json::Value cancel(jtx::Account const &dest, uint256 const &checkId)
Cancel a check.
Definition check.cpp:39
json::Value create(A const &account, A const &dest, STAmount const &sendMax)
Create a check.
json::Value cash(jtx::Account const &dest, uint256 const &checkId, STAmount const &amount)
Cash a check requiring that a specific amount be delivered.
Definition check.cpp:15
json::Value deleteCred(jtx::Account const &acc, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp: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
Keylet keylet(test::jtx::Account const &subject, test::jtx::Account const &issuer, std::string_view credType)
Definition credentials.h:21
json::Value set(jtx::Account const &account, jtx::Account const &authorize, std::vector< std::string > const &permissions)
Definition delegate.cpp:17
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 setValid(jtx::Account const &account)
Definition dids.cpp:25
json::Value create(AccountID const &account, AccountID const &to, STAmount const &amount)
Definition escrow.cpp:24
json::Value cancel(AccountID const &account, Account const &from, std::uint32_t seq)
Definition escrow.cpp:48
auto const kCondition
Definition escrow.h:85
auto const kCancelTime
Set the "CancelAfter" time tag on a JTx.
Definition escrow.h:83
auto const kFulfillment
Definition escrow.h:87
json::Value finish(AccountID const &account, AccountID const &from, std::uint32_t seq)
Definition escrow.cpp:36
std::array< std::uint8_t, 4 > const kFb1
Definition escrow.h:52
std::array< std::uint8_t, 39 > const kCb1
Definition escrow.h:54
json::Value nftPageLinks(jtx::Account const &acct, jtx::Account const &owner)
Repair the links in an NFToken directory.
json::Value set(AccountID const &account, uint256 const &vaultId, uint32_t flags)
json::Value set(AccountID const &account, uint256 const &loanBrokerID, Number principalRequested, std::uint32_t flags)
json::Value create(AccountID const &account, AccountID const &to, STAmount const &amount, NetClock::duration const &settleDelay, PublicKey const &pk, std::optional< NetClock::time_point > const &cancelAfter, std::optional< std::uint32_t > const &dstTag)
uint256 channel(AccountID const &account, AccountID const &dst, std::uint32_t seqProxyValue)
json::Value claim(AccountID const &account, uint256 const &channel, std::optional< STAmount > const &balance, std::optional< STAmount > const &amount, std::optional< Slice > const &signature, std::optional< PublicKey > const &pk)
bool channelExists(ReadView const &view, uint256 const &chan)
json::Value transfer(jtx::Account const &account, uint32_t flags, std::optional< uint256 > const &index)
Definition sponsor.cpp:52
json::Value set_fee(jtx::Account const &account, std::uint32_t flags, STAmount feeAmountDelta, std::optional< STAmount > maxFee=std::nullopt)
Definition sponsor.h:26
STAmount sponsorshipFeeBalance(jtx::Env &env, jtx::Account const &sponsor, jtx::Account const &sponsee)
Definition sponsor.cpp:93
json::Value set_reserve(jtx::Account const &account, std::uint32_t flags, std::int32_t reserveCountDelta)
Definition sponsor.h:36
json::Value set_max_fee(jtx::Account const &account, std::uint32_t flags, STAmount maxFee)
Definition sponsor.h:42
json::Value del(jtx::Account const &account)
Definition sponsor.cpp:42
json::Value set(jtx::Account const &account, uint32_t flags, std::optional< int32_t > const reserveCountDelta, std::optional< STAmount > const feeAmountDelta, std::optional< STAmount > const maxFee)
Definition sponsor.cpp:22
json::Value create(Account const &account, std::uint32_t count)
Create one of more tickets.
Definition ticket.cpp:16
json::Value mint(jtx::Account const &account, std::uint32_t nfTokenTaxon)
Mint an NFToken.
Definition token.cpp:23
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 sponsoringAccountCount(Env const &env, Account const &account)
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
std::uint32_t sponsoringOwnerCount(Env const &env, Account const &account)
FeatureBitset testableAmendments()
Definition Env.h:92
json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
void incLgrSeqForAccDel(jtx::Env &env, jtx::Account const &acc, std::uint32_t margin=0)
json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:14
std::uint32_t sponsoredOwnerCount(Env const &env, Account const &account)
json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:18
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
json::Value signers(Account const &account, std::uint32_t quorum, std::vector< Signer > const &v)
Definition multisign.cpp:31
json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:15
static XRPAmount reserve(jtx::Env &env, std::uint32_t count)
static STAmount accountReserve(jtx::Env &env, std::uint32_t count=1)
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
static void adjustAccountXRPBalance(jtx::Env &env, jtx::Account const &account, STAmount const &balanceTo)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
constexpr FlagValue spfSponsorFee
Definition TxFlags.h:459
@ telINSUF_FEE_P
Definition TER.h:43
@ telENV_RPC_FAILED
Definition TER.h:54
@ terNO_PERMISSION
Definition TER.h:228
@ terINSUF_FEE_B
Definition TER.h:212
@ terNO_DELEGATE_PERMISSION
Definition TER.h:226
@ terNO_ACCOUNT
Definition TER.h:213
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:108
constexpr FlagValue tfInnerBatchTxn
Definition TxFlags.h:44
PreflightResult preflight(ServiceRegistry &registry, Rules const &rules, STTx const &tx, ApplyFlags flags, beast::Journal j)
Gate a transaction based on static information.
bool isXRP(AccountID const &c)
Definition AccountID.h:84
PreclaimResult preclaim(PreflightResult const &preflightResult, ServiceRegistry &registry, OpenView const &view)
Gate a transaction based on static ledger information.
ApplyResult apply(ServiceRegistry &registry, OpenView &view, STTx const &tx, ApplyFlags flags, beast::Journal journal)
Apply a transaction to an OpenView.
Definition apply.cpp:122
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:13
@ tefNOT_MULTI_SIGNING
Definition TER.h:173
TenthBips< std::uint32_t > TenthBips32
Definition Units.h:454
TenthBips< std::uint16_t > TenthBips16
Definition Units.h:453
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
BaseUInt< 192 > MPTID
MPTID is a 192-bit value representing MPT Issuance ID, which is a concatenation of a 32-bit sequence ...
Definition UintTypes.h:54
MPTID makeMptID(std::uint32_t const sequence, AccountID const &account)
Definition Indexes.cpp:184
@ TapNone
Definition ApplyView.h:28
@ temINVALID
Definition TER.h:98
@ temINVALID_FLAG
Definition TER.h:99
@ temMALFORMED
Definition TER.h:75
@ temDISABLED
Definition TER.h:102
@ temBAD_AMOUNT
Definition TER.h:77
@ temBAD_SIGNATURE
Definition TER.h:93
@ temREDUNDANT
Definition TER.h:100
@ temBAD_SIGNER
Definition TER.h:103
bool isTesSuccess(TER x) noexcept
Definition TER.h:676
TERSubset< CanCvtToTER > TER
Definition TER.h:647
@ tecPSEUDO_ACCOUNT
Definition TER.h:365
@ tecNO_LINE_INSUF_RESERVE
Definition TER.h:295
@ tecINSUF_RESERVE_LINE
Definition TER.h:291
@ tecINSUFF_FEE
Definition TER.h:305
@ tecUNFUNDED_PAYMENT
Definition TER.h:288
@ tecNO_ENTRY
Definition TER.h:309
@ tecNO_SPONSOR_PERMISSION
Definition TER.h:372
@ tecFAILED_PROCESSING
Definition TER.h:289
@ tecINSUFFICIENT_RESERVE
Definition TER.h:310
@ tecLIMIT_EXCEEDED
Definition TER.h:364
@ tecNO_PERMISSION
Definition TER.h:308
@ tecHAS_OBLIGATIONS
Definition TER.h:320
@ tecNO_DST
Definition TER.h:293
@ tecUNFUNDED
Definition TER.h:298
TypedField< STAccount > SF_ACCOUNT
Definition SField.h:354
ApplyResult doApply(PreclaimResult const &preclaimResult, ServiceRegistry &registry, OpenView &view)
Apply a prechecked transaction to an OpenView.
BaseUInt< 256 > uint256
Definition base_uint.h:580
constexpr FlagValue spfSponsorReserve
Definition TxFlags.h:460
@ tesSUCCESS
Definition TER.h:245
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
uint256 key
Definition Keylet.h:21
json::Value jv
Definition JTx.h:28
Set the sequence number on a JTx.
Definition seq.h:16
A signer in a SignerList.
Definition multisign.h:27