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, features};
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, features};
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(
1079 std::string("Transfer Sponsor ") +
1080 (features[fixCleanup3_4_0] ? "(fixCleanup3_4_0 enabled)"
1081 : "(fixCleanup3_4_0 disabled)"));
1082 using namespace test::jtx;
1083
1084 // Verify preflight checks
1085 {
1086 Env env{*this, features};
1087 Account const alice("alice");
1088 Account const bob("bob");
1089 Account const sponsor("sponsor");
1090 env.fund(XRP(10000), alice, bob, sponsor);
1091 env.close();
1092
1093 // Test invalid flags for SponsorshipTransfer
1094 {
1095 // Invalid flag is provided
1097 alice,
1098 (tfSponsorshipCreate | tfSponsorshipReassign | tfSponsorshipEnd) + 1),
1100
1101 // No SponsorshipTransfer flag is provided
1102 env(sponsor::transfer(alice, 0), Ter(temINVALID_FLAG));
1103
1104 // Only one of the three valid flags can be set.
1105 // Setting more than one flag is invalid
1106 for (auto flag : {
1107 tfSponsorshipCreate | tfSponsorshipReassign,
1108 tfSponsorshipCreate | tfSponsorshipEnd,
1109 tfSponsorshipReassign | tfSponsorshipEnd,
1110 tfSponsorshipCreate | tfSponsorshipReassign | tfSponsorshipEnd,
1111 })
1112 env(sponsor::transfer(alice, flag), Ter(temINVALID_FLAG));
1113 }
1114
1115 // Malformed tests for tfSponsorshipCreate
1116 {
1117 // No sponsor field present
1118 env(sponsor::transfer(alice, tfSponsorshipCreate), Ter(temMALFORMED));
1119
1120 // Sponsor field present without spfSponsorReserve
1121 env(sponsor::transfer(alice, tfSponsorshipCreate),
1124
1125 // Sponsee field present
1126 env(sponsor::transfer(alice, tfSponsorshipCreate),
1129 Ter(temMALFORMED));
1130 }
1131
1132 // Malformed tests for tfSponsorshipReassign
1133 {
1134 // No sponsor field present
1135 env(sponsor::transfer(alice, tfSponsorshipReassign), Ter(temMALFORMED));
1136
1137 // Sponsor field present without spfSponsorReserve
1138 env(sponsor::transfer(alice, tfSponsorshipReassign),
1141
1142 // Sponsee field present
1143 env(sponsor::transfer(alice, tfSponsorshipReassign),
1146 Ter(temMALFORMED));
1147 }
1148
1149 // Malformed tests for tfSponsorshipEnd
1150 {
1151 // Sponsor field present
1152 env(sponsor::transfer(alice, tfSponsorshipEnd),
1154 Ter(temMALFORMED));
1155
1156 // SponsorFlags field present
1157 auto tx = sponsor::transfer(alice, tfSponsorshipEnd);
1158 tx[sfSponsorFlags.jsonName] = spfSponsorFee;
1159 env(tx, Ter(temINVALID_FLAG));
1160
1161 // Account = Sponsee
1162 env(sponsor::transfer(alice, tfSponsorshipEnd),
1163 sponsor::SponseeAcc(alice),
1164 Ter(temMALFORMED));
1165 }
1166
1167 // Post-fixCleanup3_5_0, a zero ObjectID is malformed.
1168 // Pre-fixCleanup3_5_0 path is unreachable so it is not testable.
1169 if (features[fixCleanup3_5_0])
1170 {
1171 UInt256 const zeroObjectID{};
1172
1173 env(sponsor::transfer(alice, tfSponsorshipEnd, zeroObjectID), Ter(temMALFORMED));
1174
1175 env(sponsor::transfer(alice, tfSponsorshipCreate, zeroObjectID),
1177 Sig(sfSponsorSignature, sponsor),
1178 Ter(temMALFORMED));
1179
1180 env(sponsor::transfer(alice, tfSponsorshipReassign, zeroObjectID),
1182 Sig(sfSponsorSignature, sponsor),
1183 Ter(temMALFORMED));
1184 }
1185 }
1186
1187 {
1188 // Invalid SponsorshipEnd permission (sponsor object/sponsor account)
1189 Env env{*this, features};
1190 Account const alice("alice");
1191 Account const bob("bob");
1192 Account const charlie("charlie");
1193 Account const sponsor("sponsor");
1194 env.fund(XRP(10000), alice, bob, sponsor);
1195 env.close();
1196
1197 {
1198 // sponsor object
1199 env.fund(XRP(1000), charlie);
1200 env.close();
1201 env(deposit::auth(alice, charlie),
1203 Sig(sfSponsorSignature, sponsor));
1204 env.close();
1205
1206 auto const keylet = keylet::depositPreauth(alice, charlie);
1207 env(sponsor::transfer(bob, tfSponsorshipEnd, keylet.key),
1208 sponsor::SponseeAcc(alice),
1210 }
1211 {
1212 // sponsor object
1213 env(sponsor::transfer(alice, tfSponsorshipCreate),
1215 Sig(sfSponsorSignature, sponsor));
1216 env.close();
1217
1218 env(sponsor::transfer(bob, tfSponsorshipEnd),
1219 sponsor::SponseeAcc(alice),
1221 }
1222 {
1223 // The provided sfSponsee account does not exist
1224 // when ending sponsorship.
1225 Account const ghost("ghost"); // never funded, absent from ledger
1226 env(sponsor::transfer(sponsor, tfSponsorshipEnd),
1227 sponsor::SponseeAcc(ghost),
1229 }
1230 }
1231
1232 {
1233 // sponsor account
1234 Env env{*this, features};
1235 Account const alice("alice");
1236 Account const bob("bob");
1237 Account const sponsor1("sponsor1");
1238 Account const sponsor2("sponsor2");
1239 env.fund(XRP(10000), alice, bob, sponsor1, sponsor2);
1240
1241 // sfSponsor provided but sfSponsorSignature not provided
1242 env(sponsor::transfer(alice, tfSponsorshipCreate),
1243 sponsor::As(sponsor1, spfSponsorReserve),
1244 Ter(temMALFORMED));
1245 env.close();
1246
1247 adjustAccountXRPBalance(env, sponsor1, accountReserve(env, 2) - drops(1));
1248
1249 env(sponsor::transfer(alice, tfSponsorshipCreate),
1250 sponsor::As(sponsor1, spfSponsorReserve),
1251 Sig(sfSponsorSignature, sponsor1),
1253 env.close();
1254
1255 adjustAccountXRPBalance(env, sponsor1, accountReserve(env, 2));
1256
1257 env(sponsor::transfer(alice, tfSponsorshipCreate),
1258 sponsor::As(sponsor1, spfSponsorReserve),
1259 Sig(sfSponsorSignature, sponsor1));
1260 env.close();
1261
1262 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
1263 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
1264 BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
1265 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
1266 BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
1267 BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 1);
1268 auto const sle1 = env.le(keylet::account(alice));
1269 BEAST_EXPECT(sle1->isFieldPresent(sfSponsor));
1270 BEAST_EXPECT(sle1->getAccountID(sfSponsor) == sponsor1.id());
1271
1272 // transfer sponsor
1273 adjustAccountXRPBalance(env, sponsor2, accountReserve(env, 2) - drops(1));
1274
1275 env(sponsor::transfer(alice, tfSponsorshipReassign),
1276 sponsor::As(sponsor2, spfSponsorReserve),
1277 Sig(sfSponsorSignature, sponsor2),
1279 env.close();
1280
1281 adjustAccountXRPBalance(env, sponsor2, accountReserve(env, 2));
1282
1283 env(sponsor::transfer(alice, tfSponsorshipReassign),
1284 sponsor::As(sponsor2, spfSponsorReserve),
1285 Sig(sfSponsorSignature, sponsor2));
1286 env.close();
1287
1288 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
1289 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
1290 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0);
1291 BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
1292 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
1293 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
1294 BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
1295 BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
1296 BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 1);
1297 BEAST_EXPECT(
1298 !env.le(keylet::account(sponsor1))->isFieldPresent(sfSponsoringAccountCount));
1299 auto const sle2 = env.le(keylet::account(alice));
1300 BEAST_EXPECT(sle2->isFieldPresent(sfSponsor));
1301 BEAST_EXPECT(sle2->getAccountID(sfSponsor) == sponsor2.id());
1302
1303 // Reassign to the current sponsor is a no-op and is rejected
1304 env(sponsor::transfer(alice, tfSponsorshipReassign),
1305 sponsor::As(sponsor2, spfSponsorReserve),
1306 Sig(sfSponsorSignature, sponsor2),
1308 env.close();
1309
1310 BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 1);
1311
1312 // sponsor 2 accounts
1313 adjustAccountXRPBalance(env, sponsor2, accountReserve(env, 3));
1314 env(sponsor::transfer(bob, tfSponsorshipCreate),
1315 sponsor::As(sponsor2, spfSponsorReserve),
1316 Sig(sfSponsorSignature, sponsor2));
1317 env.close();
1318
1319 // dissolve sponsors
1320 adjustAccountXRPBalance(env, alice, accountReserve(env, 1) - drops(1));
1321
1322 env(sponsor::transfer(alice, tfSponsorshipEnd), Ter(tecINSUFFICIENT_RESERVE));
1323 env.close();
1324
1325 adjustAccountXRPBalance(env, alice, accountReserve(env, 1));
1326
1327 env(sponsor::transfer(alice, tfSponsorshipEnd));
1328 env.close();
1329
1330 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
1331 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
1332 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0);
1333 BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
1334 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
1335 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
1336 BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
1337 BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
1338 BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 1);
1339 auto const sle3 = env.le(keylet::account(alice));
1340 BEAST_EXPECT(!sle3->isFieldPresent(sfSponsor));
1341
1342 env(sponsor::transfer(bob, tfSponsorshipEnd));
1343 env.close();
1344
1345 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
1346 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
1347 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0);
1348 BEAST_EXPECT(sponsoringOwnerCount(env, bob) == 0);
1349 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
1350 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
1351 BEAST_EXPECT(sponsoringAccountCount(env, bob) == 0);
1352 BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
1353 BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0);
1354 BEAST_EXPECT(
1355 !env.le(keylet::account(sponsor2))->isFieldPresent(sfSponsoringAccountCount));
1356 auto const sle4 = env.le(keylet::account(bob));
1357 BEAST_EXPECT(!sle4->isFieldPresent(sfSponsor));
1358
1359 // not sponsored
1360 env(sponsor::transfer(bob, tfSponsorshipEnd), Ter(tecNO_PERMISSION));
1361 env.close();
1362 }
1363 {
1364 // dissolve account sponsorship from sponsor
1365 Env env{*this, features};
1366 Account const alice("alice");
1367 Account const bob("bob");
1368 Account const sponsor("sponsor");
1369 env.fund(XRP(10000), alice, bob, sponsor);
1370 env.close();
1371
1372 env(sponsor::transfer(alice, tfSponsorshipCreate),
1374 Sig(sfSponsorSignature, sponsor));
1375 env.close();
1376
1377 BEAST_EXPECT(env.le(alice)->getAccountID(sfSponsor) == sponsor.id());
1378 BEAST_EXPECT(sponsoringAccountCount(env, sponsor) == 1);
1379
1380 env(sponsor::transfer(sponsor, tfSponsorshipEnd), sponsor::SponseeAcc(alice));
1381 env.close();
1382
1383 BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfSponsor));
1384 BEAST_EXPECT(sponsoringAccountCount(env, sponsor) == 0);
1385 }
1386
1387 {
1388 // sponsor object (co-signing)
1389 Env env{*this, features};
1390 Account const alice("alice");
1391 Account const bob("bob");
1392 Account const sponsor1("sponsor1");
1393 Account const sponsor2("sponsor2");
1394 env.fund(XRP(10000), alice, bob, sponsor1, sponsor2);
1395 env.close();
1396
1397 adjustAccountXRPBalance(env, sponsor1, reserve(env, 1) - drops(1));
1398 adjustAccountXRPBalance(env, sponsor2, reserve(env, 1) - drops(1));
1399
1400 auto const seq = env.seq(alice);
1401 env(check::create(alice, bob, XRP(1)));
1402 env.close();
1403
1404 auto const checkId = keylet::check(alice, SeqProxy::rawSequence(seq)).key;
1405 BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr);
1406
1407 env(sponsor::transfer(alice, tfSponsorshipCreate, checkId),
1408 sponsor::As(sponsor1, spfSponsorReserve),
1409 Sig(sfSponsorSignature, sponsor1),
1411 env.close();
1412
1413 env(pay(alice, sponsor1, drops(1)));
1414 env.close();
1415
1416 // Invalid ObjectID (not found)
1418 alice, tfSponsorshipCreate, keylet::check(alice, SeqProxy::rawSequence(0)).key),
1419 sponsor::As(sponsor1, spfSponsorReserve),
1420 Sig(sfSponsorSignature, sponsor1),
1421 Ter(tecNO_ENTRY));
1422 env.close();
1423
1424 // Invalid Owner
1425 env(sponsor::transfer(bob, tfSponsorshipCreate, checkId),
1426 sponsor::As(sponsor1, spfSponsorReserve),
1427 Sig(sfSponsorSignature, sponsor1),
1429 env.close();
1430
1431 // Reassign an object that is not sponsored yet
1432 env(sponsor::transfer(alice, tfSponsorshipReassign, checkId),
1433 sponsor::As(sponsor1, spfSponsorReserve),
1434 Sig(sfSponsorSignature, sponsor1),
1436 env.close();
1437
1438 // Valid Owner
1439 env(sponsor::transfer(alice, tfSponsorshipCreate, checkId),
1440 sponsor::As(sponsor1, spfSponsorReserve),
1441 Sig(sfSponsorSignature, sponsor1));
1442 env.close();
1443
1444 BEAST_EXPECT(ownerCount(env, alice) == 1);
1445 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
1446 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
1447 BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
1448 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 1);
1449 BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
1450 BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
1451 auto const sle1 = env.le(keylet::unchecked(checkId));
1452 BEAST_EXPECT(sle1->isFieldPresent(sfSponsor));
1453 BEAST_EXPECT(sle1->getAccountID(sfSponsor) == sponsor1.id());
1454
1455 // Create on an object that is already sponsored
1456 env(sponsor::transfer(alice, tfSponsorshipCreate, checkId),
1457 sponsor::As(sponsor2, spfSponsorReserve),
1458 Sig(sfSponsorSignature, sponsor2),
1460 env.close();
1461
1462 // Reassign to the current sponsor is a no-op and is rejected
1463 env(sponsor::transfer(alice, tfSponsorshipReassign, checkId),
1464 sponsor::As(sponsor1, spfSponsorReserve),
1465 Sig(sfSponsorSignature, sponsor1),
1467 env.close();
1468
1469 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 1);
1470
1471 // transfer sponsor
1472 env(sponsor::transfer(alice, tfSponsorshipReassign, checkId),
1473 sponsor::As(sponsor2, spfSponsorReserve),
1474 Sig(sfSponsorSignature, sponsor2),
1476
1477 env(pay(alice, sponsor2, drops(1)));
1478 env.close();
1479
1480 env(sponsor::transfer(alice, tfSponsorshipReassign, checkId),
1481 sponsor::As(sponsor2, spfSponsorReserve),
1482 Sig(sfSponsorSignature, sponsor2));
1483 env.close();
1484
1485 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
1486 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
1487 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0);
1488 BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
1489 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
1490 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
1491 BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
1492 BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
1493 BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0);
1494 auto const sle2 = env.le(keylet::unchecked(checkId));
1495 BEAST_EXPECT(sle2->isFieldPresent(sfSponsor));
1496 BEAST_EXPECT(sle2->getAccountID(sfSponsor) == sponsor2.id());
1497
1498 // dissolve sponsor: ending an object sponsorship now (fixCleanup3_4_0) requires the
1499 // sponsee to be able to self-fund the object's reserve.
1500 adjustAccountXRPBalance(env, alice, reserve(env, 1) - drops(1));
1501
1502 if (features[fixCleanup3_4_0])
1503 {
1504 // Under-funded: End is rejected until alice can self-fund.
1505 env(sponsor::transfer(alice, tfSponsorshipEnd, checkId),
1507 env.close();
1508
1509 adjustAccountXRPBalance(env, alice, reserve(env, 1));
1510 }
1511
1512 env(sponsor::transfer(alice, tfSponsorshipEnd, checkId));
1513 env.close();
1514
1515 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
1516 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
1517 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0);
1518 BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
1519 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
1520 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
1521 BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
1522 BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
1523 BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0);
1524 BEAST_EXPECT(
1525 !env.le(keylet::account(sponsor2))->isFieldPresent(sfSponsoringOwnerCount));
1526 auto const sle3 = env.le(keylet::unchecked(checkId));
1527 BEAST_EXPECT(!sle3->isFieldPresent(sfSponsor));
1528
1529 // Ending sponsorship on an object that is not sponsored (a ticket,
1530 // which cannot be sponsored) is rejected.
1531 adjustAccountXRPBalance(env, alice, reserve(env, 2));
1532 auto const ticketSeq = env.seq(alice);
1533 env(ticket::create(alice, 1));
1534 env.close();
1535 auto ticketId = keylet::ticket(alice, SeqProxy::rawTicket(ticketSeq + 1)).key;
1536 BEAST_EXPECT(env.le(keylet::unchecked(ticketId)));
1537 env(sponsor::transfer(alice, tfSponsorshipEnd, ticketId), Ter(tecNO_PERMISSION));
1538 env.close();
1539 env(noop(alice), ticket::Use(ticketSeq + 1));
1540 env.close();
1541 }
1542 {
1543 // sponsor object (pre-funded + no ltSponsorship entry)
1544 Env env{*this, features};
1545 Account const alice("alice");
1546 Account const bob("bob");
1547 Account const sponsor1("sponsor1");
1548 Account const sponsor2("sponsor2");
1549 env.fund(XRP(10000), alice, bob, sponsor1, sponsor2);
1550 env.close();
1551
1552 auto const seq = env.seq(alice);
1553 env(check::create(alice, bob, XRP(1)));
1554 env.close();
1555
1556 auto const checkId = keylet::check(alice, SeqProxy::rawSequence(seq)).key;
1557 BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr);
1558
1559 env(sponsor::transfer(alice, tfSponsorshipCreate, checkId),
1560 sponsor::As(sponsor1, spfSponsorReserve),
1562 env.close();
1563
1564 env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice));
1565 env.close();
1566
1567 env(sponsor::transfer(alice, tfSponsorshipCreate, checkId),
1568 sponsor::As(sponsor2, spfSponsorReserve));
1569 env.close();
1570
1571 env(sponsor::transfer(alice, tfSponsorshipReassign, checkId),
1572 sponsor::As(sponsor1, spfSponsorReserve),
1574 env.close();
1575 }
1576 {
1577 // sponsor object (pre-funded)
1578 Env env{*this, features};
1579 Account const alice("alice");
1580 Account const bob("bob");
1581 Account const sponsor1("sponsor1");
1582 Account const sponsor2("sponsor2");
1583 env.fund(XRP(10000), alice, bob, sponsor1, sponsor2);
1584 env.close();
1585
1586 auto const seq = env.seq(alice);
1587 env(check::create(alice, bob, XRP(1)));
1588 env.close();
1589
1590 auto const checkId = keylet::check(alice, SeqProxy::rawSequence(seq)).key;
1591 BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr);
1592
1593 // insufficient reserve count
1594 env(sponsor::set_fee(sponsor1, 0, XRP(100)), sponsor::SponseeAcc(alice));
1595 env.close();
1596 env(sponsor::transfer(alice, tfSponsorshipCreate, checkId),
1597 sponsor::As(sponsor1, spfSponsorReserve),
1599 env.close();
1600
1601 env(sponsor::set_reserve(sponsor1, 0, 100), sponsor::SponseeAcc(alice));
1602 env.close();
1603
1604 env(sponsor::transfer(alice, tfSponsorshipCreate, checkId),
1605 sponsor::As(sponsor1, spfSponsorReserve));
1606 env.close();
1607
1608 BEAST_EXPECT(ownerCount(env, alice) == 1);
1609 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
1610 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
1611 BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
1612 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 1);
1613 BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
1614 BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
1615 auto checkSle = env.le(keylet::unchecked(checkId));
1616 BEAST_EXPECT(checkSle->isFieldPresent(sfSponsor));
1617 BEAST_EXPECT(checkSle->getAccountID(sfSponsor) == sponsor1.id());
1618 auto sponsor1Sle = env.le(keylet::sponsorship(sponsor1, alice));
1619 BEAST_EXPECT(sponsor1Sle->getFieldU32(sfRemainingOwnerCount) == 99);
1620
1621 // Reassign to the current sponsor is rejected and must not draw
1622 // down the pre-funded reserve budget
1623 env(sponsor::transfer(alice, tfSponsorshipReassign, checkId),
1624 sponsor::As(sponsor1, spfSponsorReserve),
1626 env.close();
1627
1628 sponsor1Sle = env.le(keylet::sponsorship(sponsor1, alice));
1629 BEAST_EXPECT(sponsor1Sle->getFieldU32(sfRemainingOwnerCount) == 99);
1630 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 1);
1631
1632 // transfer sponsor
1633 env(sponsor::set_reserve(sponsor2, 0, 100), sponsor::SponseeAcc(alice));
1634 env.close();
1635
1636 env(sponsor::transfer(alice, tfSponsorshipReassign, checkId),
1637 sponsor::As(sponsor2, spfSponsorReserve));
1638 env.close();
1639
1640 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
1641 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
1642 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0);
1643 BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
1644 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
1645 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
1646 BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
1647 BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
1648 BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0);
1649 checkSle = env.le(keylet::unchecked(checkId));
1650 BEAST_EXPECT(checkSle->isFieldPresent(sfSponsor));
1651 BEAST_EXPECT(checkSle->getAccountID(sfSponsor) == sponsor2.id());
1652 sponsor1Sle = env.le(keylet::sponsorship(sponsor1, alice));
1653 BEAST_EXPECT(sponsor1Sle->getFieldU32(sfRemainingOwnerCount) == 99);
1654 auto sponsor2Sle = env.le(keylet::sponsorship(sponsor2, alice));
1655 BEAST_EXPECT(sponsor2Sle->getFieldU32(sfRemainingOwnerCount) == 99);
1656
1657 // dissolve sponsor
1658 adjustAccountXRPBalance(env, alice, reserve(env, 1));
1659 env(sponsor::transfer(alice, tfSponsorshipEnd, checkId));
1660 env.close();
1661
1662 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
1663 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor1) == 0);
1664 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor2) == 0);
1665 BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
1666 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 0);
1667 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
1668 BEAST_EXPECT(sponsoringAccountCount(env, alice) == 0);
1669 BEAST_EXPECT(sponsoringAccountCount(env, sponsor1) == 0);
1670 BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 0);
1671 BEAST_EXPECT(
1672 !env.le(keylet::account(sponsor2))->isFieldPresent(sfSponsoringOwnerCount));
1673 checkSle = env.le(keylet::unchecked(checkId));
1674 BEAST_EXPECT(!checkSle->isFieldPresent(sfSponsor));
1675 sponsor2Sle = env.le(keylet::sponsorship(sponsor2, alice));
1676 BEAST_EXPECT(sponsor2Sle->getFieldU32(sfRemainingOwnerCount) == 99);
1677 }
1678
1679 {
1680 // Dissolve object sponsorship from sponsor(no-ltSponsorship)
1681 Env env{*this, features};
1682 Account const alice("alice");
1683 Account const bob("bob");
1684 Account const sponsor("sponsor");
1685 env.fund(XRP(10000), alice, bob, sponsor);
1686 env.close();
1687
1688 auto const seq = env.seq(alice);
1689 env(check::create(alice, bob, XRP(1)));
1690 env.close();
1691
1692 auto const checkId = keylet::check(alice, SeqProxy::rawSequence(seq)).key;
1693 BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr);
1694
1695 env(sponsor::transfer(alice, tfSponsorshipCreate, checkId),
1697 Sig(sfSponsorSignature, sponsor));
1698 env.close();
1699
1700 BEAST_EXPECT(
1701 env.le(keylet::unchecked(checkId))->getAccountID(sfSponsor) == sponsor.id());
1702 BEAST_EXPECT(ownerCount(env, alice) == 1);
1703 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
1704 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
1705
1706 // not the owner of the object
1707 env(sponsor::transfer(sponsor, tfSponsorshipEnd, checkId), Ter(tecNO_PERMISSION));
1708 env.close();
1709
1710 env(sponsor::transfer(sponsor, tfSponsorshipEnd, checkId), sponsor::SponseeAcc(alice));
1711 env.close();
1712
1713 BEAST_EXPECT(!env.le(keylet::unchecked(checkId))->isFieldPresent(sfSponsor));
1714 BEAST_EXPECT(ownerCount(env, alice) == 1);
1715 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
1716 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
1717 }
1718
1719 {
1720 // Dissolve object sponsorship from sponsor (with ltSponsorship)
1721 Env env{*this, features};
1722 Account const alice("alice");
1723 Account const bob("bob");
1724 Account const sponsor("sponsor");
1725 env.fund(XRP(10000), alice, bob, sponsor);
1726 env.close();
1727
1728 auto const seq = env.seq(alice);
1729 env(check::create(alice, bob, XRP(1)));
1730 env.close();
1731
1732 auto const checkId = keylet::check(alice, SeqProxy::rawSequence(seq)).key;
1733 BEAST_EXPECT(env.le(keylet::unchecked(checkId)) != nullptr);
1734
1735 env(sponsor::transfer(alice, tfSponsorshipCreate, checkId),
1737 Sig(sfSponsorSignature, sponsor));
1738 env.close();
1739
1740 env(sponsor::set_reserve(sponsor, 0, 100), sponsor::SponseeAcc(alice));
1741 env.close();
1742
1743 BEAST_EXPECT(
1744 env.le(keylet::unchecked(checkId))->getAccountID(sfSponsor) == sponsor.id());
1745 BEAST_EXPECT(ownerCount(env, alice) == 1);
1746 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
1747 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
1748 BEAST_EXPECT(
1749 env.le(keylet::sponsorship(sponsor, alice))->getFieldU32(sfRemainingOwnerCount) ==
1750 100);
1751
1752 // not the owner of the object
1753 env(sponsor::transfer(sponsor, tfSponsorshipEnd, checkId), Ter(tecNO_PERMISSION));
1754 env.close();
1755
1756 env(sponsor::transfer(sponsor, tfSponsorshipEnd, checkId), sponsor::SponseeAcc(alice));
1757 env.close();
1758
1759 BEAST_EXPECT(!env.le(keylet::unchecked(checkId))->isFieldPresent(sfSponsor));
1760 BEAST_EXPECT(ownerCount(env, alice) == 1);
1761 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
1762 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
1763 BEAST_EXPECT(
1764 env.le(keylet::sponsorship(sponsor, alice))->getFieldU32(sfRemainingOwnerCount) ==
1765 100);
1766 }
1767
1768 {
1769 // sponsor trustline
1770 Account const alice("alice");
1771 Account const bob("bob");
1772 Account const sponsor("sponsor");
1773
1774 auto const& highAcc = alice > bob ? alice : bob;
1775 auto const& lowAcc = alice > bob ? bob : alice;
1776
1777 for (bool const isIssuerHigh : {false, true})
1778 {
1779 Env env{*this, features};
1780 env.fund(XRP(10000), alice, bob, sponsor);
1781 env.close();
1782
1783 auto const& issuer = isIssuerHigh ? highAcc : lowAcc;
1784 auto const& user = isIssuerHigh ? lowAcc : highAcc;
1785
1786 auto const usd = issuer["usd"];
1787 auto const currency = usd.currency;
1788
1789 env(trust(user, issuer["usd"](100)));
1790 env.close();
1791
1792 auto const trustId = keylet::trustLine(user, issuer, currency);
1793 BEAST_EXPECT(env.le(trustId));
1794
1795 // transfer sponsor
1796 env(sponsor::transfer(user, tfSponsorshipCreate, trustId.key),
1798 Sig(sfSponsorSignature, sponsor));
1799 env.close();
1800
1801 BEAST_EXPECT(env.le(trustId));
1802
1803 BEAST_EXPECT(
1804 env.le(trustId)->getAccountID(isIssuerHigh ? sfLowSponsor : sfHighSponsor) ==
1805 sponsor.id());
1806 BEAST_EXPECT(
1807 !env.le(trustId)->isFieldPresent(isIssuerHigh ? sfHighSponsor : sfLowSponsor));
1808
1809 // dissolve sponsor
1810 env(sponsor::transfer(user, tfSponsorshipEnd, trustId.key));
1811 env.close();
1812
1813 BEAST_EXPECT(env.le(trustId));
1814 BEAST_EXPECT(
1815 !env.le(trustId)->isFieldPresent(isIssuerHigh ? sfLowSponsor : sfHighSponsor));
1816 BEAST_EXPECT(
1817 !env.le(trustId)->isFieldPresent(isIssuerHigh ? sfHighSponsor : sfLowSponsor));
1818 }
1819 }
1820
1821 {
1822 // invalid transfer
1823 Env env{*this, features};
1824 Account const alice("alice");
1825 Account const bob("bob");
1826 Account const sponsor("sponsor");
1827 env.fund(XRP(10000), alice, bob, sponsor);
1828 env.close();
1829
1830 // create owner dir
1831 env(ticket::create(alice, 1));
1832 env.close();
1833
1834 // AccountRoot
1835 // Amendments
1836 // LedgerHashes
1837 // FeeSettings
1838 // NegativeUNL
1839 // DirNode
1840 auto const keylets = {
1841 keylet::account(alice),
1842 // keylet::amendments(),
1843 keylet::skip(),
1845 // keylet::negativeUNL(),
1846 keylet::ownerDir(alice),
1847 };
1848 for (auto const& keylet : keylets)
1849 {
1850 env(sponsor::transfer(alice, tfSponsorshipCreate, keylet.key),
1852 Sig(sfSponsorSignature, sponsor),
1854 }
1855 }
1856
1857 {
1858 // existing owner objects that are outside the v1 SponsorshipTransfer
1859 // object allow-list
1860 Env env{*this, features};
1861 Account const alice("alice");
1862 Account const sponsor("sponsor");
1863 env.fund(XRP(10000), alice, sponsor);
1864 env.close();
1865
1866 auto const checkBlocked = [&](Account const& account, UInt256 const& objectID) {
1867 env(sponsor::transfer(account, tfSponsorshipCreate, objectID),
1869 Sig(sfSponsorSignature, sponsor),
1871 env.close();
1872 };
1873
1874 auto const ticketSeq = env.seq(alice);
1875 env(ticket::create(alice, 1));
1876 env.close();
1877 auto const ticketID = keylet::ticket(alice, SeqProxy::rawTicket(ticketSeq + 1)).key;
1878 BEAST_EXPECT(env.le(keylet::unchecked(ticketID)));
1879 checkBlocked(alice, ticketID);
1880
1881 env(did::setValid(alice));
1882 env.close();
1883 auto const didKeylet = keylet::did(alice.id());
1884 BEAST_EXPECT(env.le(didKeylet));
1885 checkBlocked(alice, didKeylet.key);
1886
1887 env(token::mint(alice, 0u));
1888 env.close();
1889 auto const nftPageKeylet = keylet::nftokenPageMax(alice);
1890 BEAST_EXPECT(env.le(nftPageKeylet));
1891 checkBlocked(alice, nftPageKeylet.key);
1892
1893 Account const borrower("borrower");
1894 env.fund(XRP(1000000), borrower);
1895 env.close();
1896
1897 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
1898 Vault const vault{env};
1899 // Under featureLendingProtocolV1_1 LoanBrokerSet::preclaim only
1900 // accepts closed-ended vaults; build one and advance past
1901 // SubscriptionDate before creating a loan.
1902 auto [vaultTx, vaultKeylet, subscriptionDate] =
1903 vault.createClosedEnded({.owner = alice, .asset = xrpAsset});
1904 env(vaultTx);
1905 env.close();
1906
1907 env(vault.deposit(
1908 {.depositor = alice, .id = vaultKeylet.key, .amount = xrpAsset(1000)}));
1909 env.close();
1910
1911 vault.closePastSubscription(subscriptionDate);
1912
1913 auto const brokerKeylet =
1914 keylet::loanBroker(alice.id(), SeqProxy::rawSequence(env.seq(alice)));
1915 env(loan_broker::set(alice, vaultKeylet.key),
1916 loan_broker::kDebtMaximum(xrpAsset(1000).value()),
1920 env.close();
1921
1922 auto const loanKeylet = keylet::loan(brokerKeylet.key, SeqProxy::rawSequence(1));
1923 env(loan::set(borrower, brokerKeylet.key, xrpAsset(100).value()),
1924 Sig(sfCounterpartySignature, alice),
1925 Fee(env.current()->fees().base * 2));
1926 env.close();
1927 BEAST_EXPECT(env.le(loanKeylet));
1928 checkBlocked(borrower, loanKeylet.key);
1929 }
1930 }
1931
1932 void
1934 {
1935 using namespace test::jtx;
1936
1937 testcase("Sponsor Fee");
1938
1939 {
1940 // co-signing
1941 Env env{*this, testableAmendments()};
1942 Account const alice("alice");
1943 Account const bob("bob");
1944 Account const sponsor("sponsor");
1945 env.fund(XRP(10000), alice, bob);
1946 env.close();
1947
1948 {
1949 // Fee should be checked before sponsor permission, otherwise a tec
1950 // result from a later check could cause context reset 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 env.fund(XRP(1000), sponsor);
1967 env.close();
1968
1969 {
1970 // Sponsor pays the Fee
1971 auto aliceBalance = env.balance(alice);
1972 auto bobBalance = env.balance(bob);
1973 auto sponsorBalance = env.balance(sponsor);
1974
1975 auto const sendAmt = XRP(100);
1976 auto const feeAmt = XRP(10);
1977 env(pay(alice, bob, sendAmt),
1978 Fee(feeAmt),
1980 Sig(sfSponsorSignature, sponsor));
1981 env.close();
1982 BEAST_EXPECT(env.balance(alice) == aliceBalance - sendAmt);
1983 BEAST_EXPECT(env.balance(bob) == bobBalance + sendAmt);
1984 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance - feeAmt);
1985 }
1986
1987 {
1988 // insufficient balance to pay Fee
1989 auto aliceBalance = env.balance(alice);
1990 auto bobBalance = env.balance(bob);
1991 auto sponsorBalance = env.balance(sponsor);
1992
1993 env(pay(alice, bob, XRP(100)),
1994 Fee(XRP(2000)),
1996 Sig(sfSponsorSignature, sponsor),
1998 env.close();
1999 BEAST_EXPECT(env.balance(alice) == aliceBalance);
2000 BEAST_EXPECT(env.balance(bob) == bobBalance);
2001 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
2002 }
2003
2004 {
2005 // Fee is paid by Sponsor
2006 // on context reset (tec error)
2007 auto aliceBalance = env.balance(alice);
2008 auto bobBalance = env.balance(bob);
2009 auto sponsorBalance = env.balance(sponsor);
2010 auto const feeAmt = XRP(10);
2011
2012 env(pay(alice, bob, XRP(20000)),
2013 Fee(feeAmt),
2015 Sig(sfSponsorSignature, sponsor),
2017 env.close();
2018
2019 BEAST_EXPECT(env.balance(alice) == aliceBalance);
2020 BEAST_EXPECT(env.balance(bob) == bobBalance);
2021 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance - feeAmt);
2022 }
2023
2024 {
2025 // A co-signed sponsor pays the fee from its own balance, but
2026 // must never be charged into its own account reserve. With a
2027 // balance of exactly reserve + fee the fee is still payable,
2028 // charging the sponsor down to precisely its reserve.
2029 auto const feeAmt = XRP(10);
2030 adjustAccountXRPBalance(env, sponsor, reserve(env, 0) + feeAmt);
2031 auto const sponsorBalance = env.balance(sponsor);
2032
2033 env(noop(alice),
2034 Fee(feeAmt),
2036 Sig(sfSponsorSignature, sponsor),
2037 Ter(tesSUCCESS));
2038 env.close();
2039
2040 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance - feeAmt);
2041 BEAST_EXPECT(env.balance(sponsor) == reserve(env, 0));
2042 }
2043
2044 {
2045 // below reserve
2046 adjustAccountXRPBalance(env, sponsor, env.current()->fees().reserve);
2047
2048 env(noop(alice),
2049 Fee(env.current()->fees().base),
2051 Sig(sfSponsorSignature, sponsor),
2053 env.close();
2054
2055 env(noop(alice),
2056 Fee(XRP(10)),
2058 Sig(sfSponsorSignature, sponsor),
2060 env.close();
2061 }
2062 }
2063
2064 {
2065 // pre funded
2066 Env env{*this, testableAmendments()};
2067 Account const alice("alice");
2068 Account const bob("bob");
2069 Account const sponsor("sponsor");
2070 env.fund(XRP(10000), alice, bob, sponsor);
2071 env.close();
2072
2073 {
2074 // Fee should be checked before sponsor permission, otherwise a tec
2075 // result from a later check could cause context reset to pay Fee.
2076 auto aliceBalance = env.balance(alice);
2077 auto bobBalance = env.balance(bob);
2078 auto sponsorBalance = env.balance(sponsor);
2079
2080 env(pay(alice, bob, XRP(100)),
2081 Fee(XRP(2000)),
2084 env.close();
2085 BEAST_EXPECT(env.balance(alice) == aliceBalance);
2086 BEAST_EXPECT(env.balance(bob) == bobBalance);
2087 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
2088 }
2089
2090 env(sponsor::set_fee(sponsor, 0, XRP(100)), sponsor::SponseeAcc(alice));
2091 env.close();
2092
2093 {
2094 // Sponsor pays the Fee
2095 auto aliceBalance = env.balance(alice);
2096 auto bobBalance = env.balance(bob);
2097 auto sponsorBalance = env.balance(sponsor);
2098 auto sponsorFee = sponsor::sponsorshipFeeBalance(env, sponsor, alice);
2099
2100 auto const sendAmt = XRP(100);
2101 auto const feeAmt = XRP(10);
2102 env(pay(alice, bob, sendAmt), Fee(feeAmt), sponsor::As(sponsor, spfSponsorFee));
2103 env.close();
2104
2105 BEAST_EXPECT(env.balance(alice) == aliceBalance - sendAmt);
2106 BEAST_EXPECT(env.balance(bob) == bobBalance + sendAmt);
2107 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
2108 BEAST_EXPECT(
2109 sponsor::sponsorshipFeeBalance(env, sponsor, alice) == sponsorFee - feeAmt);
2110 }
2111
2112 {
2113 // insufficient balance to pay Fee
2114 {
2115 // > FeeAmount
2116 auto aliceBalance = env.balance(alice);
2117 auto bobBalance = env.balance(bob);
2118 auto sponsorBalance = env.balance(sponsor);
2119 auto sponsorFee = sponsor::sponsorshipFeeBalance(env, sponsor, alice);
2120
2121 env(pay(alice, bob, XRP(100)),
2122 Fee(XRP(90) + drops(1)),
2125 env.close();
2126
2127 BEAST_EXPECT(env.balance(alice) == aliceBalance);
2128 BEAST_EXPECT(env.balance(bob) == bobBalance);
2129 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
2130 BEAST_EXPECT(sponsor::sponsorshipFeeBalance(env, sponsor, alice) == sponsorFee);
2131 }
2132 // use all FeeAmount
2133 {
2134 // = FeeAmount
2135 auto aliceBalance = env.balance(alice);
2136 auto bobBalance = env.balance(bob);
2137 auto sponsorBalance = env.balance(sponsor);
2138
2139 env(pay(alice, bob, XRP(100)),
2140 Fee(XRP(90)),
2142 Ter(tesSUCCESS));
2143 env.close();
2144
2145 BEAST_EXPECT(env.balance(alice) == aliceBalance - XRP(100));
2146 BEAST_EXPECT(env.balance(bob) == bobBalance + XRP(100));
2147 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
2148 BEAST_EXPECT(
2149 !env.le(keylet::sponsorship(sponsor, alice))->isFieldPresent(sfFeeAmount));
2150 }
2151
2152 // reset FeeAmount and MaxFee
2154 env.close();
2155 env(sponsor::set_fee(sponsor, 0, XRP(10), XRP(1)), sponsor::SponseeAcc(alice));
2156 env.close();
2157
2158 {
2159 // > MaxFee
2160 auto aliceBalance = env.balance(alice);
2161 auto bobBalance = env.balance(bob);
2162 auto sponsorBalance = env.balance(sponsor);
2163 auto sponsorFee = sponsor::sponsorshipFeeBalance(env, sponsor, alice);
2164
2165 env(pay(alice, bob, XRP(100)),
2166 Fee(XRP(1) + drops(1)),
2169 env.close();
2170
2171 BEAST_EXPECT(env.balance(alice) == aliceBalance);
2172 BEAST_EXPECT(env.balance(bob) == bobBalance);
2173 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
2174 BEAST_EXPECT(sponsor::sponsorshipFeeBalance(env, sponsor, alice) == sponsorFee);
2175 }
2176 }
2177
2178 {
2179 // Fee is paid by Sponsor
2180 // on context reset (tec error)
2181 auto aliceBalance = env.balance(alice);
2182 auto bobBalance = env.balance(bob);
2183 auto sponsorBalance = env.balance(sponsor);
2184 auto sponsorFee = sponsor::sponsorshipFeeBalance(env, sponsor, alice);
2185 auto const feeAmt = XRP(1);
2186
2187 env(pay(alice, bob, XRP(20000)),
2188 Fee(feeAmt),
2191 env.close();
2192
2193 BEAST_EXPECT(env.balance(alice) == aliceBalance);
2194 BEAST_EXPECT(env.balance(bob) == bobBalance);
2195 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
2196 BEAST_EXPECT(
2197 sponsor::sponsorshipFeeBalance(env, sponsor, alice) == sponsorFee - feeAmt);
2198 }
2199
2200 // make sfFeeAmount absent if tec error and all Fee is paid
2201 {
2202 // reset FeeAmount and MaxFee
2204 env(sponsor::set_fee(sponsor, 0, XRP(10)), sponsor::SponseeAcc(alice));
2205 env.close();
2206
2207 BEAST_EXPECT(
2208 env.le(keylet::sponsorship(sponsor, alice))->isFieldPresent(sfFeeAmount));
2209 auto sponsorAvailableFee = sponsor::sponsorshipFeeBalance(env, sponsor, alice);
2210 env(check::cancel(alice, UInt256(1)),
2211 Fee(sponsorAvailableFee),
2213 Ter(tecNO_ENTRY));
2214 env.close();
2215 BEAST_EXPECT(
2216 !env.le(keylet::sponsorship(sponsor, alice))->isFieldPresent(sfFeeAmount));
2217 }
2218 }
2219
2220 // MaxFee cap is enforced in reset() for tec-failing transactions.
2221 // On a closed ledger view (!view.open()), checkFee returns tecINSUFF_FEE when
2222 // Fee > MaxFee (not terINSUF_FEE_B), triggering reset()
2223 {
2224 Env env{*this, testableAmendments()};
2225 Account const alice("alice");
2226 Account const carol("sponsor");
2227
2228 env.fund(XRP(10000), alice, carol);
2229 env.close();
2230
2231 // FeeAmount=1000 drops, MaxFee=10 drops
2232 env(sponsor::set_fee(carol, 0, drops(1000), drops(10)), sponsor::SponseeAcc(alice));
2233 env.close();
2234
2235 // Apply directly against the closed ledger view (open_ = false) so that
2236 // checkFee returns tecINSUFF_FEE and reset() is invoked.
2237 OpenView overlay(&*env.closed());
2238
2239 auto jt = env.jt(
2240 noop(alice),
2241 Fee(drops(1000)),
2242 Seq(env.seq(alice)),
2243 sponsor::As(carol, spfSponsorFee));
2244
2245 auto const result = xrpl::apply(env.app(), overlay, *jt.stx, TapNone, env.journal);
2246 BEAST_EXPECT(result.ter == tecINSUFF_FEE);
2247 BEAST_EXPECT(result.applied);
2248
2249 // Only MaxFee (10 drops) must be deducted, not the full 1000 drops.
2250 auto const sle = overlay.read(keylet::sponsorship(carol.id(), alice.id()));
2251 BEAST_EXPECT(sle);
2252 BEAST_EXPECT(sle->isFieldPresent(sfFeeAmount));
2253 BEAST_EXPECT(sle->getFieldAmount(sfFeeAmount) == drops(990)); // 1000 - MaxFee(10)
2254 }
2255
2256 // LedgerStateFix charges an owner-reserve fee and can claim that fee
2257 // while returning tecFAILED_PROCESSING. That path must be safe when the
2258 // fee is pre-funded by a sponsorship object.
2259 {
2260 Env env{*this, testableAmendments()};
2261 Account const alice("alice");
2262 Account const sponsor("sponsor");
2263 env.fund(XRP(1000), alice, sponsor);
2264 env.close();
2265
2266 auto const fixFee = drops(env.current()->fees().increment);
2267 env(sponsor::set_fee(sponsor, 0, fixFee), sponsor::SponseeAcc(alice));
2268 env.close();
2269
2270 env(ledger_state_fix::nftPageLinks(alice, alice),
2271 Fee(fixFee),
2274
2275 if (auto const sle = env.le(keylet::sponsorship(sponsor, alice)); BEAST_EXPECT(sle))
2276 BEAST_EXPECT(!sle->isFieldPresent(sfFeeAmount));
2277 }
2278
2279 // If preclaim saw spendable sponsored FeeAmount but the apply view no
2280 // longer has it, the fee path should fail cleanly instead of throwing.
2281 {
2282 Env env{*this, testableAmendments()};
2283 Account const alice("alice");
2284 Account const sponsor("sponsor");
2285 env.fund(XRP(1000), alice, sponsor);
2286 env.close();
2287
2288 auto const fixFee = drops(env.current()->fees().increment);
2289 env(sponsor::set_fee(sponsor, 0, fixFee), sponsor::SponseeAcc(alice));
2290 env.close();
2291
2292 OpenView overlay(&*env.closed());
2293 auto jt = env.jt(
2294 ledger_state_fix::nftPageLinks(alice, alice),
2295 Fee(fixFee),
2297
2298 auto const pf = preflight(env.app(), overlay.rules(), *jt.stx, TapNone, env.journal);
2299 BEAST_EXPECT(isTesSuccess(pf.ter));
2300 auto const pc = preclaim(pf, env.app(), overlay);
2301 BEAST_EXPECT(isTesSuccess(pc.ter));
2302
2303 auto const original = overlay.read(keylet::sponsorship(sponsor, alice));
2304 if (BEAST_EXPECT(original))
2305 {
2306 auto sle = std::make_shared<SLE>(*original);
2307 sle->makeFieldAbsent(sfFeeAmount);
2308 overlay.rawReplace(sle);
2309 }
2310
2311 auto const result = doApply(pc, env.app(), overlay);
2312 BEAST_EXPECT(result.ter == terINSUF_FEE_B);
2313 BEAST_EXPECT(!result.applied);
2314 }
2315
2316 // test lsfSponsorshipRequireSignForFee
2317 {
2318 Env env{*this, testableAmendments()};
2319 Account const alice("alice");
2320 Account const bob("bob");
2321 Account const sponsor("sponsor");
2322 env.fund(XRP(10000), alice, bob, sponsor);
2323 env.close();
2324
2325 // set flag
2326 env(sponsor::set_fee(sponsor, tfSponsorshipSetRequireSignForFee, XRP(10)),
2327 sponsor::SponseeAcc(alice));
2328 env.close();
2329
2330 env(pay(alice, bob, XRP(100)),
2331 Fee(XRP(10)),
2334 env.close();
2335
2336 BEAST_EXPECT(
2337 env.le(keylet::sponsorship(sponsor, alice))->getFieldAmount(sfFeeAmount) ==
2338 XRP(10));
2339
2340 // clear flag
2341 env(sponsor::set(sponsor, tfSponsorshipClearRequireSignForFee),
2342 sponsor::SponseeAcc(alice));
2343 env.close();
2344
2345 // Payment is re-applied
2346 BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice))->isFieldPresent(sfFeeAmount));
2347 }
2348
2349 // RequireSignForFee: co-signing should succeed
2350 {
2351 Env env{*this, testableAmendments()};
2352 Account const alice("alice");
2353 Account const bob("bob");
2354 Account const sponsor("sponsor");
2355 env.fund(XRP(10000), alice, bob, sponsor);
2356 env.close();
2357
2358 // set flag
2359 env(sponsor::set_fee(sponsor, tfSponsorshipSetRequireSignForFee, XRP(10)),
2360 sponsor::SponseeAcc(alice));
2361 env.close();
2362
2363 // pre-funded (no sig) should fail
2364 env(pay(alice, bob, XRP(100)),
2365 Fee(XRP(1)),
2368 env.close();
2369
2370 // co-signing (with sig) should succeed
2371 env(pay(alice, bob, XRP(100)),
2372 Fee(XRP(1)),
2374 Sig(sfSponsorSignature, sponsor),
2375 Ter(tesSUCCESS));
2376 env.close();
2377
2378 BEAST_EXPECT(
2379 env.le(keylet::sponsorship(sponsor, alice))->getFieldAmount(sfFeeAmount) == XRP(9));
2380 }
2381 }
2382
2383 void
2385 {
2386 testcase("Sponsor Account");
2387 using namespace test::jtx;
2388
2389 Account const alice("alice");
2390 Account const sponsor("sponsor");
2391 Account const sponsor2("sponsor2");
2392 Account const sponsor3("sponsor3");
2393 Account const bob("bob");
2394 Account const charlie("charlie");
2395 Account const dave("dave");
2396 Account const gw("gw");
2397 auto const usd = gw["usd"];
2398
2399 {
2400 // Disabled
2401 Env env{*this, testableAmendments() - featureSponsor};
2402 env.fund(XRP(10000), alice, sponsor);
2403 env.close();
2404 env(pay(alice, bob, XRP(100)), Txflags(tfSponsorCreatedAccount), Ter(temDISABLED));
2405 env.close();
2406 }
2407
2408 Env env{*this, testableAmendments()};
2409 env.fund(XRP(10000), alice, sponsor, sponsor2, sponsor3);
2410 env.close();
2411
2412 // Invalid flags
2413 for (auto flag : {
2414 tfNoRippleDirect,
2415 tfPartialPayment,
2416 tfLimitQuality,
2417 })
2418 {
2419 env(pay(alice, bob, XRP(100)),
2420 Txflags(tfSponsorCreatedAccount | flag),
2422 env.close();
2423 }
2424
2425 // Invalid amount(iou)
2426 env(pay(alice, bob, usd(100)), Txflags(tfSponsorCreatedAccount), Ter(temBAD_AMOUNT));
2427 env.close();
2428
2429 // Sponsored account creation is reserve sponsorship and is only supported for direct XRP
2430 // payments.
2431 env(pay(alice, bob, drops(1)),
2432 Txflags(tfSponsorCreatedAccount),
2433 Sendmax(usd(2)),
2434 Ter(temINVALID));
2435 env.close();
2436
2437 env(pay(alice, bob, drops(1)),
2438 Txflags(tfSponsorCreatedAccount),
2439 Path(~XRP),
2440 Ter(temINVALID));
2441 env.close();
2442
2443 // Account is not sponsored by normal Sponsor specification
2444 {
2445 env(pay(alice, bob, drops(env.current()->fees().accountReserve(0, 1))),
2447 Sig(sfSponsorSignature, sponsor));
2448 env.close();
2449
2450 auto const bobSle = env.le(keylet::account(bob));
2451 BEAST_EXPECT(!bobSle->isFieldPresent(sfSponsor));
2452 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
2453 BEAST_EXPECT(sponsoringAccountCount(env, sponsor) == 0);
2454 }
2455
2456 // Use tfSponsorCreatedAccount to sponsor an account
2457 {
2458 // to funded account
2459 env(pay(sponsor2, bob, drops(1)),
2460 Txflags(tfSponsorCreatedAccount),
2461 Fee(XRP(1)),
2463 env.close();
2464
2465 BEAST_EXPECT(env.balance(sponsor2) == XRP(9999));
2466
2467 // to non-funded account / insufficient balance for reserve
2468 env(pay(sponsor2, charlie, XRP(9999) - env.current()->fees().reserve + drops(1)),
2469 Txflags(tfSponsorCreatedAccount),
2471 env.close();
2472
2473 // to non-funded account
2474 auto const sponsor2BalanceBefore = env.balance(sponsor2);
2475 env(pay(sponsor2, charlie, drops(1)), Txflags(tfSponsorCreatedAccount), Fee(XRP(1)));
2476 env.close();
2477
2478 auto const charlieSle = env.le(keylet::account(charlie));
2479 BEAST_EXPECT(charlieSle->isFieldPresent(sfSponsor));
2480 BEAST_EXPECT(charlieSle->getAccountID(sfSponsor) == sponsor2.id());
2481 BEAST_EXPECT(sponsoredOwnerCount(env, charlie) == 0);
2482 BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 1);
2483 // verify sponsor balance decreased by payment + Fee
2484 BEAST_EXPECT(env.balance(sponsor2) == sponsor2BalanceBefore - drops(1) - XRP(1));
2485 }
2486 {
2487 // insufficient reserve to sponsor acount
2488
2489 auto const sendAmount = drops(1);
2490 // 2 account reserve + send amount
2491 auto const requireBalance = accountReserve(env, 2) + sendAmount;
2492 adjustAccountXRPBalance(env, sponsor3, requireBalance - drops(1));
2493 env(pay(sponsor3, dave, sendAmount),
2494 Txflags(tfSponsorCreatedAccount),
2495 Fee(XRP(1)),
2497 env.close();
2498
2499 adjustAccountXRPBalance(env, sponsor3, requireBalance);
2500 env(pay(sponsor3, dave, sendAmount),
2501 Txflags(tfSponsorCreatedAccount),
2502 Fee(XRP(1)),
2503 Ter(tesSUCCESS));
2504 env.close();
2505 }
2506 }
2507
2508 void
2510 {
2511 using namespace test::jtx;
2512 {
2513 testcase("SponsorshipRequireSignForReserve");
2514
2515 Env env{*this, testableAmendments()};
2516 Account const alice("alice");
2517 Account const bob("bob");
2518 Account const sponsor("sponsor");
2519 env.fund(XRP(10000), alice, bob, sponsor);
2520 env.close();
2521
2522 // set flag
2523 env(sponsor::set_reserve(sponsor, tfSponsorshipSetRequireSignForReserve, 10),
2524 sponsor::SponseeAcc(alice));
2525 env.close();
2526
2527 env(check::create(alice, bob, XRP(100)),
2528 Fee(XRP(10)),
2531
2532 BEAST_EXPECT(ownerCount(env, alice) == 0);
2533 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
2534 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
2535
2536 // clear flag
2537 env(sponsor::set_reserve(sponsor, tfSponsorshipClearRequireSignForReserve, 1),
2538 sponsor::SponseeAcc(alice));
2539 env.close();
2540
2541 // CheckCreate is re-applied
2542 BEAST_EXPECT(ownerCount(env, alice) == 1);
2543 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
2544 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
2545 }
2546
2547 {
2548 testcase("SponsorshipRequireSignForFee");
2549
2550 Env env{*this, testableAmendments()};
2551 Account const alice("alice");
2552 Account const bob("bob");
2553 Account const sponsor("sponsor");
2554 env.fund(XRP(10000), alice, bob, sponsor);
2555 env.close();
2556
2557 // set flag
2558 env(sponsor::set_fee(sponsor, tfSponsorshipSetRequireSignForFee, XRP(10)),
2559 sponsor::SponseeAcc(alice));
2560 env.close();
2561
2562 env(check::create(alice, bob, XRP(100)),
2563 Fee(XRP(10)),
2566
2567 BEAST_EXPECT(ownerCount(env, alice) == 0);
2568 BEAST_EXPECT(
2569 env.le(keylet::sponsorship(sponsor, alice))->getFieldAmount(sfFeeAmount) ==
2570 XRP(10));
2571
2572 // clear flag
2573 env(sponsor::set(sponsor, tfSponsorshipClearRequireSignForFee),
2574 sponsor::SponseeAcc(alice));
2575 env.close();
2576
2577 // CheckCreate is re-applied
2578 BEAST_EXPECT(ownerCount(env, alice) == 1);
2579 BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice))->isFieldPresent(sfFeeAmount));
2580 }
2581 }
2582
2583 void
2585 {
2586 testcase("SponsorReserveSimple");
2587 using namespace test::jtx;
2588 Env env{*this, testableAmendments()};
2589 Account const alice("alice");
2590 Account const bob("bob");
2591 Account const sponsor("sponsor");
2592
2593 env.fund(XRP(10000), alice, bob, sponsor);
2594 env.close();
2595
2596 if (cosigning)
2597 {
2598 adjustAccountXRPBalance(env, sponsor, reserve(env, 1) - drops(1));
2599
2600 env(check::create(alice, bob, XRP(100)),
2602 Sig(sfSponsorSignature, sponsor),
2604 env.close();
2605
2607
2608 env(check::create(alice, bob, XRP(100)),
2610 Sig(sfSponsorSignature, sponsor),
2611 Ter(tesSUCCESS));
2612 env.close();
2613 }
2614 else
2615 {
2616 env(sponsor::set_reserve(sponsor, 0, 250), sponsor::SponseeAcc(alice));
2617 env.close();
2618
2619 adjustAccountXRPBalance(env, sponsor, reserve(env, 2) - drops(1));
2620
2621 env(check::create(alice, bob, XRP(100)),
2624 env.close();
2625
2627
2628 env(check::create(alice, bob, XRP(100)),
2630 Ter(tesSUCCESS));
2631 env.close();
2632 }
2633 }
2634
2635 // test helper for both cosigning and pre-funded sponsorship
2636 template <typename SubmitCallback>
2637 void
2639 test::jtx::Env& env,
2640 bool cosigning,
2641 jtx::Account const& sponsor,
2642 jtx::Account const& sponsee,
2643 uint32_t reserveCount,
2644 uint32_t sponsorReserveCount,
2645 TER insufficientReserveResult,
2646 SubmitCallback callback,
2647 std::optional<std::function<void()>> expected = std::nullopt)
2648 {
2649 using namespace test::jtx;
2650 auto const sponseeOwnerCountBefore = ownerCount(env, sponsee);
2651 auto const sponseeSponsoredOwnerCountBefore = sponsoredOwnerCount(env, sponsee);
2652 auto const sponseeSponsoringOwnerCountBefore = sponsoringOwnerCount(env, sponsee);
2653 auto const sponsorOwnerCountBefore = ownerCount(env, sponsor);
2654 auto const sponsorSponsoredOwnerCountBefore = sponsoredOwnerCount(env, sponsor);
2655 auto const sponsorSponsoringOwnerCountBefore = sponsoringOwnerCount(env, sponsor);
2656
2657 std::optional<Sig> sponsorSig =
2658 cosigning ? std::optional<Sig>(Sig(sfSponsorSignature, sponsor)) : std::nullopt;
2659
2660 auto const sponsorCurrentOwnerCount = ownerCount(env, sponsor) -
2662
2663 auto submit = [&](TER ter) {
2664 return [&, ter](json::Value const& jv, auto const&... fN) {
2665 if (sponsorSig)
2666 {
2667 env(jv, fN..., sponsor::As(sponsor, spfSponsorReserve), *sponsorSig, Ter(ter));
2668 }
2669 else
2670 {
2671 env(jv, fN..., sponsor::As(sponsor, spfSponsorReserve), Ter(ter));
2672 }
2673 };
2674 };
2675
2676 // Insufficient Reserve
2677 {
2678 if (cosigning)
2679 {
2681 env,
2682 sponsor,
2683 reserve(env, sponsorCurrentOwnerCount + sponsorReserveCount) - drops(1));
2684 }
2685 else
2686 {
2687 // cleanup previous sponsorship
2688 if (env.le(keylet::sponsorship(sponsor, sponsee)))
2689 {
2691 env.close();
2692 }
2693
2694 if (sponsorReserveCount > 1)
2695 {
2696 env(sponsor::set(sponsor, 0, sponsorReserveCount - 1, XRP(1)),
2697 sponsor::SponseeAcc(sponsee));
2698 }
2699 else
2700 {
2701 // just create sponsor object
2702 env(sponsor::set(sponsor, 0, std::nullopt, XRP(1)),
2703 sponsor::SponseeAcc(sponsee));
2704 }
2705 env.close();
2706 }
2707
2708 // A failed sponsored create must not consume prefunded reserve or mutate owner counts.
2709 auto const sponseeOwnerCountBeforeAttempt = ownerCount(env, sponsee);
2710 auto const sponseeSponsoredOwnerCountBeforeAttempt = sponsoredOwnerCount(env, sponsee);
2711 auto const sponseeSponsoringOwnerCountBeforeAttempt =
2712 sponsoringOwnerCount(env, sponsee);
2713 auto const sponsorOwnerCountBeforeAttempt = ownerCount(env, sponsor);
2714 auto const sponsorSponsoredOwnerCountBeforeAttempt = sponsoredOwnerCount(env, sponsor);
2715 auto const sponsorSponsoringOwnerCountBeforeAttempt =
2717 auto const sponsorshipSleBeforeAttempt = env.le(keylet::sponsorship(sponsor, sponsee));
2718 bool const reserveCountPresentBeforeAttempt = sponsorshipSleBeforeAttempt &&
2719 sponsorshipSleBeforeAttempt->isFieldPresent(sfRemainingOwnerCount);
2720 std::uint32_t const reserveCountBeforeAttempt = reserveCountPresentBeforeAttempt
2721 ? sponsorshipSleBeforeAttempt->getFieldU32(sfRemainingOwnerCount)
2722 : 0;
2723
2724 callback(env, submit(insufficientReserveResult));
2725 env.close();
2726
2727 BEAST_EXPECT(ownerCount(env, sponsee) == sponseeOwnerCountBeforeAttempt);
2728 BEAST_EXPECT(
2729 sponsoredOwnerCount(env, sponsee) == sponseeSponsoredOwnerCountBeforeAttempt);
2730 BEAST_EXPECT(
2731 sponsoringOwnerCount(env, sponsee) == sponseeSponsoringOwnerCountBeforeAttempt);
2732 BEAST_EXPECT(ownerCount(env, sponsor) == sponsorOwnerCountBeforeAttempt);
2733 BEAST_EXPECT(
2734 sponsoredOwnerCount(env, sponsor) == sponsorSponsoredOwnerCountBeforeAttempt);
2735 BEAST_EXPECT(
2736 sponsoringOwnerCount(env, sponsor) == sponsorSponsoringOwnerCountBeforeAttempt);
2737
2738 auto const sponsorshipSleAfterAttempt = env.le(keylet::sponsorship(sponsor, sponsee));
2739 BEAST_EXPECT(
2740 static_cast<bool>(sponsorshipSleAfterAttempt) ==
2741 static_cast<bool>(sponsorshipSleBeforeAttempt));
2742 if (sponsorshipSleAfterAttempt)
2743 {
2744 BEAST_EXPECT(
2745 sponsorshipSleAfterAttempt->isFieldPresent(sfRemainingOwnerCount) ==
2746 reserveCountPresentBeforeAttempt);
2747 if (reserveCountPresentBeforeAttempt)
2748 {
2749 BEAST_EXPECT(
2750 sponsorshipSleAfterAttempt->getFieldU32(sfRemainingOwnerCount) ==
2751 reserveCountBeforeAttempt);
2752 }
2753 }
2754 }
2755
2756 // Success
2757 {
2758 if (cosigning)
2759 {
2761 env, sponsor, reserve(env, sponsorCurrentOwnerCount + sponsorReserveCount));
2762 }
2763 else
2764 {
2765 // reset sponsorship
2767 env(sponsor::set(sponsor, 0, sponsorReserveCount, XRP(1)),
2768 sponsor::SponseeAcc(sponsee));
2769 env.close();
2770 }
2771 callback(env, submit(tesSUCCESS));
2772 env.close();
2773
2774 if (!cosigning)
2775 {
2776 // Prefunded success consumes the reserved owner slot before cleanup.
2777 auto const sponsorshipSle = env.le(keylet::sponsorship(sponsor, sponsee));
2778 BEAST_EXPECT(sponsorshipSle);
2779 BEAST_EXPECT(
2780 !sponsorshipSle->isFieldPresent(sfRemainingOwnerCount) ||
2781 sponsorshipSle->getFieldU32(sfRemainingOwnerCount) == 0);
2782
2783 // cleanup sponsorship
2785 env.close();
2786 }
2787 }
2788
2789 if (expected)
2790 {
2791 (*expected)();
2792 }
2793 else
2794 {
2795 BEAST_EXPECT(ownerCount(env, sponsee) - sponseeOwnerCountBefore == reserveCount);
2796 BEAST_EXPECT(
2797 sponsoredOwnerCount(env, sponsee) - sponseeSponsoredOwnerCountBefore ==
2798 sponsorReserveCount);
2799 BEAST_EXPECT(
2800 sponsoringOwnerCount(env, sponsee) - sponseeSponsoringOwnerCountBefore == 0);
2801 BEAST_EXPECT(ownerCount(env, sponsor) == sponsorOwnerCountBefore);
2802 BEAST_EXPECT(sponsoredOwnerCount(env, sponsor) == sponsorSponsoredOwnerCountBefore);
2803 BEAST_EXPECT(
2804 sponsoringOwnerCount(env, sponsor) - sponsorSponsoringOwnerCountBefore ==
2805 sponsorReserveCount);
2806 }
2807 };
2808
2809 void
2810 testCheck(bool cosigning)
2811 {
2812 testcase("Check");
2813 using namespace test::jtx;
2814 Account const alice("alice");
2815 Account const bob("bob");
2816 Account const gw("gw");
2817 Account const sponsor("sponsor");
2818 Account const sponsor2("sponsor2");
2819
2820 auto const usd = gw["usd"];
2821
2822 {
2823 Env env{*this, testableAmendments()};
2824 env.fund(XRP(10000), alice, bob, sponsor, sponsor2);
2825 env.close();
2826
2827 // CheckCreate -> Check -> CheckCancel
2828
2829 uint32_t seq = 0;
2831 env,
2832 cosigning,
2833 sponsor,
2834 alice,
2835 1,
2836 1,
2838 [&](Env& env, auto const& submit) {
2839 seq = env.seq(alice);
2840 submit(check::create(alice, bob, XRP(1)));
2841 });
2842
2843 BEAST_EXPECT(ownerCount(env, alice) == 1); // Check
2844 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
2845 BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
2846 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
2847
2848 auto const keylet = keylet::check(alice, SeqProxy::rawSequence(seq));
2849 BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor.id());
2850
2851 if (cosigning)
2852 {
2853 // transfer sponsor
2854 env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
2855 sponsor::As(sponsor2, spfSponsorReserve),
2856 Sig(sfSponsorSignature, sponsor2));
2857 env.close();
2858 }
2859 else
2860 {
2861 env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice));
2862 env.close();
2863
2864 // transfer sponsor
2865 env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
2866 sponsor::As(sponsor2, spfSponsorReserve));
2867 env.close();
2868 }
2869
2870 BEAST_EXPECT(ownerCount(env, alice) == 1); // Check
2871 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
2872 BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
2873 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
2874 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
2875
2876 BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor2.id());
2877
2878 // CheckCancel
2879 env(check::cancel(alice, keylet.key));
2880 env.close();
2881
2882 BEAST_EXPECT(ownerCount(env, alice) == 0);
2883 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
2884 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
2885 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
2886 }
2887
2888 {
2889 Env env{*this, testableAmendments()};
2890 env.fund(XRP(10000), alice, bob, sponsor);
2891 env.close();
2892
2893 // CheckCreate -> CheckCash
2894 uint32_t seq2 = 0;
2896 env,
2897 cosigning,
2898 sponsor,
2899 alice,
2900 1,
2901 1,
2903 [&](Env& env, auto const& submit) {
2904 seq2 = env.seq(alice);
2905 submit(check::create(alice, bob, XRP(1)));
2906 });
2907
2908 BEAST_EXPECT(ownerCount(env, bob) == 0);
2909 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
2910
2911 // CheckCash
2912 auto const checkId2 = keylet::check(alice, SeqProxy::rawSequence(seq2)).key;
2913 env(check::cash(bob, checkId2, XRP(1)));
2914 env.close();
2915
2916 BEAST_EXPECT(ownerCount(env, alice) == 0);
2917 BEAST_EXPECT(ownerCount(env, bob) == 0);
2918 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
2919 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
2920 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
2921 }
2922
2923 // RippleState sponsor (CheckCashMakesTrustLine)
2924 {
2925 Env env{*this, testableAmendments()};
2926 env.fund(XRP(10000), alice, bob, gw, sponsor, sponsor2);
2927 env.close();
2928
2929 env.trust(usd(100), alice);
2930 env.close();
2931 env(pay(gw, alice, usd(100)));
2932 env.close();
2933
2934 // CheckCreate -> CheckCash
2935 uint32_t seq2 = 0;
2937 env,
2938 cosigning,
2939 sponsor,
2940 alice,
2941 1,
2942 1,
2944 [&](Env& env, auto const& submit) {
2945 seq2 = env.seq(alice);
2946 submit(check::create(alice, bob, usd(1)));
2947 });
2948
2949 BEAST_EXPECT(ownerCount(env, bob) == 0);
2950 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
2951
2952 auto const keylet = keylet::check(alice, SeqProxy::rawSequence(seq2));
2953 BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor.id());
2954
2955 // CheckCash
2957 env,
2958 cosigning,
2959 sponsor,
2960 bob,
2961 1,
2962 1,
2964 [&](Env& env, auto const& submit) { submit(check::cash(bob, keylet.key, usd(1))); },
2965 [&]() {
2966 BEAST_EXPECT(ownerCount(env, alice) == 1); // RippleState
2967 BEAST_EXPECT(ownerCount(env, bob) == 1); // RippleState
2968 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
2969 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1);
2970 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
2971
2972 // the newly-created trust line carries the sponsor on
2973 // bob's side, and no sponsor on the counterparty side
2974 bool const bobLow = bob.id() < gw.id();
2975 auto const lineSle = env.le(keylet::trustLine(bob, gw, usd.currency));
2976 BEAST_EXPECT(lineSle);
2977 if (lineSle)
2978 {
2979 BEAST_EXPECT(
2980 lineSle->getAccountID(bobLow ? sfLowSponsor : sfHighSponsor) ==
2981 sponsor.id());
2982 BEAST_EXPECT(
2983 !lineSle->isFieldPresent(bobLow ? sfHighSponsor : sfLowSponsor));
2984 }
2985 });
2986 }
2987
2988 // MPT sponsor: cashing an MPT check creates a sponsored MPToken for
2989 // the casher. Unlike the trust-line path, this returns
2990 // tecINSUFFICIENT_RESERVE (not tecNO_LINE_INSUF_RESERVE) on shortfall.
2991 {
2992 Env env{*this, testableAmendments()};
2993 env.fund(XRP(10000), bob, sponsor, sponsor2);
2994 env.close();
2995
2996 MPTTester mptGw(env, gw, {.holders = {alice}});
2997 mptGw.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer});
2998 mptGw.authorize({.account = alice});
2999 auto const mpt = mptGw["MPT"];
3000 env(pay(gw, alice, mpt(10'000)));
3001 env.close();
3002
3003 // CheckCreate (alice -> bob) paying the MPT
3004 uint32_t seq2 = 0;
3006 env,
3007 cosigning,
3008 sponsor,
3009 alice,
3010 1,
3011 1,
3013 [&](Env& env, auto const& submit) {
3014 seq2 = env.seq(alice);
3015 submit(check::create(alice, bob, mpt(1)));
3016 });
3017
3018 auto const checkKeylet = keylet::check(alice, SeqProxy::rawSequence(seq2));
3019 BEAST_EXPECT(env.le(checkKeylet)->getAccountID(sfSponsor) == sponsor.id());
3020 BEAST_EXPECT(ownerCount(env, bob) == 0);
3021
3022 // CheckCash by bob (no MPToken yet) creates a sponsored MPToken
3024 env,
3025 cosigning,
3026 sponsor,
3027 bob,
3028 1,
3029 1,
3031 [&](Env& env, auto const& submit) {
3032 submit(check::cash(bob, checkKeylet.key, mpt(1)));
3033 },
3034 [&]() {
3035 BEAST_EXPECT(ownerCount(env, bob) == 1); // MPToken
3036 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1);
3037 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
3038 BEAST_EXPECT(
3039 env.le(keylet::mptoken(mptGw.issuanceID(), bob))->getAccountID(sfSponsor) ==
3040 sponsor.id());
3041 });
3042 }
3043 }
3044
3045 void
3046 testDelegate(bool cosigning)
3047 {
3048 testcase("Delegate");
3049 using namespace test::jtx;
3050 Account const alice("alice");
3051 Account const bob("bob");
3052 Account const sponsor("sponsor");
3053 Account const sponsor2("sponsor2");
3054
3055 {
3056 Env env{*this, testableAmendments()};
3057 env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
3058 env.close();
3059
3060 // DelegateSet
3062 env,
3063 cosigning,
3064 sponsor,
3065 alice,
3066 1,
3067 1,
3069 [&](Env& env, auto const& submit) {
3070 submit(delegate::set(alice, bob, {"Payment"}));
3071 });
3072
3073 // the created Delegate object carries the sponsor
3074 auto const keylet = keylet::delegate(alice, bob);
3075 BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor.id());
3076
3077 // transfer sponsor
3078 if (cosigning)
3079 {
3080 env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
3081 sponsor::As(sponsor2, spfSponsorReserve),
3082 Sig(sfSponsorSignature, sponsor2));
3083 env.close();
3084 }
3085 else
3086 {
3087 env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice));
3088 env.close();
3089
3090 env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
3091 sponsor::As(sponsor2, spfSponsorReserve));
3092 env.close();
3093 }
3094
3095 BEAST_EXPECT(ownerCount(env, alice) == 1);
3096 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
3097 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3098 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
3099 BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor2.id());
3100
3101 // delete
3102 env(delegate::set(alice, bob, {}));
3103 env.close();
3104
3105 BEAST_EXPECT(ownerCount(env, alice) == 0);
3106 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
3107 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3108 }
3109 }
3110
3111 void
3112 testDepositPreauth(bool cosigning)
3113 {
3114 testcase("DepositPreauth");
3115 using namespace test::jtx;
3116 Account const alice("alice");
3117 Account const sponsor("sponsor");
3118 Account const sponsor2("sponsor2");
3119 auto const credType = std::string("credType");
3120
3121 {
3122 Env env{*this, testableAmendments()};
3123 env.fund(XRP(1000000), alice, sponsor, sponsor2);
3124 env.close();
3125
3126 // DepositPreauthSet
3128 env,
3129 cosigning,
3130 sponsor,
3131 alice,
3132 1,
3133 1,
3135 [&](Env& env, auto const& submit) { submit(deposit::auth(alice, sponsor)); });
3136
3137 // the created DepositPreauth object carries the sponsor
3138 auto const keylet = keylet::depositPreauth(alice, sponsor);
3139 BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor.id());
3140
3141 // transfer sponsor
3142 if (cosigning)
3143 {
3144 env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
3145 sponsor::As(sponsor2, spfSponsorReserve),
3146 Sig(sfSponsorSignature, sponsor2));
3147 env.close();
3148 }
3149 else
3150 {
3151 env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice));
3152 env.close();
3153 // No sponsor signature here: this exercises the prefunded reassign path.
3154 env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
3155 sponsor::As(sponsor2, spfSponsorReserve));
3156 env.close();
3157
3158 auto const sponsor2Sle = env.le(keylet::sponsorship(sponsor2, alice));
3159 BEAST_EXPECT(sponsor2Sle);
3160 if (sponsor2Sle)
3161 {
3162 BEAST_EXPECT(
3163 !sponsor2Sle->isFieldPresent(sfRemainingOwnerCount) ||
3164 sponsor2Sle->getFieldU32(sfRemainingOwnerCount) == 0);
3165 }
3166 }
3167
3168 BEAST_EXPECT(ownerCount(env, alice) == 1);
3169 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
3170 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3171 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
3172 BEAST_EXPECT(env.le(keylet)->getAccountID(sfSponsor) == sponsor2.id());
3173
3174 // DepositPreauthDelete
3175 env(deposit::unauth(alice, sponsor));
3176 env.close();
3177
3178 BEAST_EXPECT(ownerCount(env, alice) == 0);
3179 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
3180 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3181 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
3182 }
3183
3184 {
3185 Env env{*this, testableAmendments()};
3186 env.fund(XRP(1000000), alice, sponsor);
3187 env.close();
3188 auto const authCreds = std::vector<deposit::AuthorizeCredentials>{
3189 {.issuer = sponsor, .credType = credType}};
3190 auto const preauthKeylet = keylet::depositPreauth(
3191 alice.id(),
3193 {sponsor.id(), Slice(credType.data(), credType.size())}});
3194
3195 // Cover DepositPreauth's sfAuthorizeCredentials sponsor-reserve branch.
3197 env,
3198 cosigning,
3199 sponsor,
3200 alice,
3201 1,
3202 1,
3204 [&](Env&, auto const& submit) {
3205 submit(deposit::authCredentials(alice, authCreds));
3206 });
3207
3208 // Cover sfUnauthorizeCredentials cleanup for a sponsored preauth object.
3209 BEAST_EXPECT(env.le(preauthKeylet));
3210 BEAST_EXPECT(env.le(preauthKeylet)->getAccountID(sfSponsor) == sponsor.id());
3211 BEAST_EXPECT(ownerCount(env, alice) == 1);
3212 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
3213 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
3214
3215 env(deposit::unauthCredentials(alice, authCreds));
3216 env.close();
3217
3218 BEAST_EXPECT(!env.le(preauthKeylet));
3219 BEAST_EXPECT(ownerCount(env, alice) == 0);
3220 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
3221 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3222 }
3223 }
3224
3225 void
3226 testCredential(bool cosigning)
3227 {
3228 testcase("Credential");
3229 using namespace test::jtx;
3230 Account const alice("alice");
3231 Account const bob("bob");
3232 Account const sponsor("sponsor");
3233 Account const sponsor2("sponsor2");
3234 auto const credType = std::string("test");
3235
3236 // Self-issued credential: alice creates for herself, sponsor covers reserve
3237 {
3238 Env env{*this, testableAmendments()};
3239 env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
3240 env.close();
3241
3243 env,
3244 cosigning,
3245 sponsor,
3246 alice,
3247 1,
3248 1,
3250 [&](Env& env, auto const& submit) {
3251 submit(credentials::create(alice, alice, credType));
3252 });
3253
3254 auto const credKeylet = credentials::keylet(alice, alice, credType);
3255 BEAST_EXPECT(env.le(credKeylet)->getAccountID(sfSponsor) == sponsor.id());
3256
3257 BEAST_EXPECT(ownerCount(env, alice) == 1);
3258 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
3259 BEAST_EXPECT(sponsoringOwnerCount(env, alice) == 0);
3260 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
3261
3262 // Transfer sponsor
3263 if (cosigning)
3264 {
3265 env(sponsor::transfer(alice, tfSponsorshipReassign, credKeylet.key),
3266 sponsor::As(sponsor2, spfSponsorReserve),
3267 Sig(sfSponsorSignature, sponsor2));
3268 env.close();
3269 }
3270 else
3271 {
3272 env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice));
3273 env.close();
3274 env(sponsor::transfer(alice, tfSponsorshipReassign, credKeylet.key),
3275 sponsor::As(sponsor2, spfSponsorReserve));
3276 env.close();
3277 }
3278
3279 BEAST_EXPECT(ownerCount(env, alice) == 1);
3280 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
3281 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3282 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
3283 BEAST_EXPECT(env.le(credKeylet)->getAccountID(sfSponsor) == sponsor2.id());
3284
3285 // CredentialDelete
3286 env(credentials::deleteCred(alice, alice, alice, credType));
3287 env.close();
3288
3289 BEAST_EXPECT(ownerCount(env, alice) == 0);
3290 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
3291 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3292 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
3293 }
3294
3295 // Issuer creates for subject; sponsorship transfers to subject on accept
3296 {
3297 Env env{*this, testableAmendments()};
3298 env.fund(XRP(1000000), alice, bob, sponsor);
3299 env.close();
3300
3301 // alice (issuer) creates credential for bob (subject), sponsor covers alice's reserve
3303 env,
3304 cosigning,
3305 sponsor,
3306 alice,
3307 1,
3308 1,
3310 [&](Env& env, auto const& submit) {
3311 submit(credentials::create(bob, alice, credType));
3312 });
3313
3314 // Before accept: alice (issuer) owns the credential
3315 auto const credKeylet = credentials::keylet(bob, alice, credType);
3316 BEAST_EXPECT(env.le(credKeylet)->getAccountID(sfSponsor) == sponsor.id());
3317
3318 BEAST_EXPECT(ownerCount(env, alice) == 1);
3319 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
3320 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
3321 BEAST_EXPECT(ownerCount(env, bob) == 0);
3322 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
3323
3324 // Bob accepts: issuer's sponsorship ends, bob becomes unsponsored owner
3325 env(credentials::accept(bob, alice, credType));
3326 env.close();
3327
3328 BEAST_EXPECT(ownerCount(env, alice) == 0);
3329 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
3330 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0); // original sponsorship ended
3331 BEAST_EXPECT(ownerCount(env, bob) == 1);
3332 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0); // bob owns it without a sponsor
3333
3334 // CredentialDelete by subject
3335 env(credentials::deleteCred(bob, bob, alice, credType));
3336 env.close();
3337
3338 BEAST_EXPECT(ownerCount(env, alice) == 0);
3339 BEAST_EXPECT(ownerCount(env, bob) == 0);
3340 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
3341 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
3342 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3343 }
3344
3345 // Issuer creates for subject; accept tx itself is reserve-sponsored
3346 // This exercises the checkReserve() call in CredentialAccept::doApply()
3347 // that guards the sponsor's reserve when featureSponsor is enabled.
3348 {
3349 Env env{*this, testableAmendments()};
3350 env.fund(XRP(1000000), alice, bob, sponsor);
3351 env.close();
3352
3353 // alice (issuer) creates credential for bob (subject) without sponsor
3354 env(credentials::create(bob, alice, credType));
3355 env.close();
3356
3357 auto const credKeylet = credentials::keylet(bob, alice, credType);
3358 BEAST_EXPECT(ownerCount(env, alice) == 1);
3359 BEAST_EXPECT(ownerCount(env, bob) == 0);
3360
3361 // Bob accepts with a sponsored reserve; the first attempt uses an
3362 // undercapitalized sponsor and must fail with tecINSUFFICIENT_RESERVE;
3363 // the second attempt uses a properly funded sponsor and must succeed.
3365 env,
3366 cosigning,
3367 sponsor,
3368 bob,
3369 1,
3370 1,
3372 [&](Env& env, auto const& submit) {
3373 submit(credentials::accept(bob, alice, credType));
3374 });
3375
3376 // After successful accept: alice (issuer) no longer owns the credential,
3377 // bob owns it and sponsor covers his reserve.
3378 BEAST_EXPECT(ownerCount(env, alice) == 0);
3379 BEAST_EXPECT(ownerCount(env, bob) == 1);
3380 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1);
3381 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
3382 BEAST_EXPECT(env.le(credKeylet)->getAccountID(sfSponsor) == sponsor.id());
3383
3384 // Cleanup
3385 env(credentials::deleteCred(bob, bob, alice, credType));
3386 env.close();
3387 }
3388 }
3389
3390 void
3391 testEscrow(bool cosigning)
3392 {
3393 testcase("Escrow");
3394 using namespace test::jtx;
3395 using namespace std::chrono_literals;
3396
3397 Account const alice("alice");
3398 Account const bob("bob");
3399 Account const sponsor("sponsor");
3400 Account const sponsor2("sponsor2");
3401 {
3402 // Native Escrow
3403 Env env{*this, testableAmendments()};
3404 auto const baseFee = env.current()->fees().base;
3405
3406 env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
3407 env.close();
3408
3409 // EscrowCreate
3410 uint32_t seq = 0;
3412 env,
3413 cosigning,
3414 sponsor,
3415 alice,
3416 1,
3417 1,
3419 [&](Env& env, auto const& submit) {
3420 seq = env.seq(alice);
3421 submit(
3422 escrow::create(alice, bob, XRP(100)),
3424 escrow::kCancelTime(env.now() + 100s));
3425 });
3426 BEAST_EXPECT(
3427 env.le(keylet::escrow(alice, SeqProxy::rawSequence(seq)))
3428 ->getAccountID(sfSponsor) == sponsor.id());
3429
3430 // transfer sponsor
3431 if (cosigning)
3432 {
3434 alice,
3435 tfSponsorshipReassign,
3436 keylet::escrow(alice, SeqProxy::rawSequence(seq)).key),
3437 sponsor::As(sponsor2, spfSponsorReserve),
3438 Sig(sfSponsorSignature, sponsor2));
3439 env.close();
3440 }
3441 else
3442 {
3443 env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice));
3444 env.close();
3445
3447 alice,
3448 tfSponsorshipReassign,
3449 keylet::escrow(alice, SeqProxy::rawSequence(seq)).key),
3450 sponsor::As(sponsor2, spfSponsorReserve));
3451 env.close();
3452 }
3453
3454 BEAST_EXPECT(ownerCount(env, alice) == 1);
3455 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
3456 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3457 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
3458
3459 BEAST_EXPECT(
3460 env.le(keylet::escrow(alice, SeqProxy::rawSequence(seq)))
3461 ->getAccountID(sfSponsor) == sponsor2.id());
3462
3463 // EscrowFinish
3464 env(escrow::finish(bob, alice, seq),
3467 Fee(baseFee * 150));
3468 env.close();
3469
3470 BEAST_EXPECT(ownerCount(env, alice) == 0);
3471 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
3472 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3473 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
3474 }
3475
3476 Account const gw("gw");
3477 auto const usd = gw["usd"];
3478 {
3479 // IOU Escrow
3480 Env env{*this, testableAmendments()};
3481 auto const baseFee = env.current()->fees().base;
3482
3483 env.fund(XRP(1000000), alice, bob, gw, sponsor, sponsor2);
3484 env.close();
3485
3486 env(fset(gw, asfAllowTrustLineLocking));
3487 env.close();
3488
3489 env.trust(usd(1000000), alice);
3490 env.close();
3491 env(pay(gw, alice, usd(10000)));
3492 env.close();
3493
3494 BEAST_EXPECT(ownerCount(env, alice) == 1);
3495
3496 // EscrowCreate
3497 uint32_t seq = 0;
3499 env,
3500 cosigning,
3501 sponsor,
3502 alice,
3503 1,
3504 1,
3506 [&](Env& env, auto const& submit) {
3507 seq = env.seq(alice);
3508 submit(
3509 escrow::create(alice, bob, usd(100)),
3511 escrow::kCancelTime(env.now() + 100s));
3512 });
3513
3514 BEAST_EXPECT(
3515 env.le(keylet::escrow(alice, SeqProxy::rawSequence(seq)))
3516 ->getAccountID(sfSponsor) == sponsor.id());
3517
3518 // EscrowFinish
3520 env,
3521 cosigning,
3522 sponsor2,
3523 bob,
3524 1,
3525 1,
3527 [&](Env& env, auto const& submit) {
3528 submit(
3529 escrow::finish(bob, alice, seq),
3532 Fee(baseFee * 150));
3533 });
3534
3535 BEAST_EXPECT(ownerCount(env, alice) == 1);
3536 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
3537 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3538
3539 BEAST_EXPECT(
3540 env.le(keylet::trustLine(bob, gw, usd.currency))->getAccountID(sfHighSponsor) ==
3541 sponsor2.id());
3542 }
3543 {
3544 // IOU EscrowFinish recycles reserve when the same sponsor backs
3545 // the escrow being removed and the destination line being created.
3546 Env env{*this, testableAmendments()};
3547 auto const baseFee = env.current()->fees().base;
3548
3549 env.fund(XRP(1000000), alice, bob, gw, sponsor);
3550 env.close();
3551
3552 env(fset(gw, asfAllowTrustLineLocking));
3553 env.close();
3554
3555 env.trust(usd(1000000), alice);
3556 env.close();
3557 env(pay(gw, alice, usd(10000)));
3558 env.close();
3559
3560 uint32_t seq = 0;
3562 env,
3563 cosigning,
3564 sponsor,
3565 alice,
3566 1,
3567 1,
3569 [&](Env& env, auto const& submit) {
3570 seq = env.seq(alice);
3571 submit(
3572 escrow::create(alice, bob, usd(100)),
3574 escrow::kCancelTime(env.now() + 100s));
3575 });
3576
3577 BEAST_EXPECT(
3578 env.le(keylet::escrow(alice, SeqProxy::rawSequence(seq)))
3579 ->getAccountID(sfSponsor) == sponsor.id());
3580
3581 if (cosigning)
3582 {
3584 }
3585 else
3586 {
3588 env.close();
3589 }
3590
3591 if (cosigning)
3592 {
3593 env(escrow::finish(bob, alice, seq),
3597 Sig(sfSponsorSignature, sponsor),
3598 Fee(baseFee * 150),
3599 Ter(tesSUCCESS));
3600 }
3601 else
3602 {
3603 env(escrow::finish(bob, alice, seq),
3607 Fee(baseFee * 150),
3608 Ter(tesSUCCESS));
3609 }
3610 env.close();
3611
3612 BEAST_EXPECT(ownerCount(env, alice) == 1);
3613 BEAST_EXPECT(ownerCount(env, bob) == 1);
3614 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
3615 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1);
3616 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
3617
3618 BEAST_EXPECT(
3619 env.le(keylet::trustLine(bob, gw, usd.currency))->getAccountID(sfHighSponsor) ==
3620 sponsor.id());
3621 }
3622 {
3623 // IOU Escrow cancel re-creates the owner's trust line, and the
3624 // cancel transaction's sponsor can cover that new line's reserve.
3625 // Creating an IOU escrow moves the sender's balance to the issuer,
3626 // so the sender can delete their now-zero trust line while the
3627 // escrow is pending. Cancelling returns the funds, re-creating the
3628 // line.
3629 Env env{*this, testableAmendments()};
3630
3631 env.fund(XRP(1000000), alice, bob, gw, sponsor, sponsor2);
3632 env.close();
3633
3634 env(fset(gw, asfAllowTrustLineLocking));
3635 env.close();
3636
3637 env.trust(usd(1000000), alice);
3638 env.close();
3639 env(pay(gw, alice, usd(10000)));
3640 env.close();
3641
3642 BEAST_EXPECT(ownerCount(env, alice) == 1);
3643
3644 // alice escrows her entire balance so the trust line can be removed
3645 auto const cancelAfter = env.now() + 100s;
3646 auto const seq = env.seq(alice);
3647 env(escrow::create(alice, bob, usd(10000)),
3649 escrow::kCancelTime(cancelAfter));
3650 env.close();
3651
3652 BEAST_EXPECT(ownerCount(env, alice) == 2); // trust line + escrow
3653
3654 // alice deletes her now-zero trust line while the escrow is pending
3655 env(trust(alice, usd(0)));
3656 env.close();
3657 BEAST_EXPECT(!env.le(keylet::trustLine(alice, gw, usd.currency)));
3658 BEAST_EXPECT(ownerCount(env, alice) == 1); // just the escrow
3659
3660 // advance the ledger past the cancel time
3661 for (; env.now() < cancelAfter; env.close())
3662 {
3663 }
3664
3665 // EscrowCancel by alice re-creates her trust line; the sponsor
3666 // covers the new line's reserve. On the insufficient-reserve
3667 // attempt the whole transaction rolls back, leaving the escrow
3668 // intact to be cancelled on the success attempt.
3669 bool const aliceLow = alice.id() < gw.id();
3671 env,
3672 cosigning,
3673 sponsor,
3674 alice,
3675 1,
3676 1,
3678 [&](Env& env, auto const& submit) { submit(escrow::cancel(alice, alice, seq)); },
3679 [&]() {
3680 BEAST_EXPECT(!env.le(keylet::escrow(alice, SeqProxy::rawSequence(seq))));
3681 auto const trustSle = env.le(keylet::trustLine(alice, gw, usd.currency));
3682 BEAST_EXPECT(trustSle);
3683 if (trustSle)
3684 {
3685 BEAST_EXPECT(
3686 trustSle->getAccountID(aliceLow ? sfLowSponsor : sfHighSponsor) ==
3687 sponsor.id());
3688 BEAST_EXPECT(
3689 !trustSle->isFieldPresent(aliceLow ? sfHighSponsor : sfLowSponsor));
3690 }
3691 BEAST_EXPECT(ownerCount(env, alice) == 1); // re-created trust line
3692 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
3693 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
3694 });
3695 }
3696 {
3697 // MPT Escrow
3698 Env env{*this, testableAmendments()};
3699 env.fund(XRP(1000000), bob, sponsor);
3700 env.close();
3701
3702 MPTTester mptGw(env, gw, {.holders = {alice}});
3703 mptGw.create(
3704 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer});
3705 mptGw.authorize({.account = alice});
3706 auto const mpt = mptGw["MPT"];
3707 env(pay(gw, alice, mpt(10'000)));
3708 env.close();
3709
3710 // create Escrow from alice to bob
3711 auto const seq = env.seq(alice);
3712 env(escrow::create(alice, bob, mpt(100)),
3714 escrow::kCancelTime(env.now() + 100s));
3715 env.close();
3716
3717 BEAST_EXPECT(ownerCount(env, alice) == 2);
3718 BEAST_EXPECT(ownerCount(env, bob) == 0);
3719 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3720
3721 // finish Escrow: bob has no MPToken, so finishing creates one and
3722 // the finish transaction's sponsor covers its reserve. The MPT
3723 // create-token path returns tecINSUFFICIENT_RESERVE (not
3724 // tecNO_LINE_INSUF_RESERVE) when the sponsor is underfunded.
3726 env,
3727 cosigning,
3728 sponsor,
3729 bob,
3730 1,
3731 1,
3733 [&](Env& env, auto const& submit) {
3734 submit(
3735 escrow::finish(bob, alice, seq),
3738 Fee(XRP(1)));
3739 });
3740
3741 BEAST_EXPECT(ownerCount(env, alice) == 1);
3742 BEAST_EXPECT(ownerCount(env, bob) == 1);
3743 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1);
3744 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
3745 BEAST_EXPECT(
3746 env.le(keylet::mptoken(mptGw.issuanceID(), bob))->getAccountID(sfSponsor) ==
3747 sponsor.id());
3748 }
3749 {
3750 // MPT EscrowFinish has the same reserve recycling behavior as IOU
3751 // when it creates the destination MPToken.
3752 Env env{*this, testableAmendments()};
3753 auto const baseFee = env.current()->fees().base;
3754 env.fund(XRP(1000000), bob, sponsor);
3755 env.close();
3756
3757 MPTTester mptGw(env, gw, {.holders = {alice}});
3758 mptGw.create(
3759 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanEscrow | tfMPTCanTransfer});
3760 mptGw.authorize({.account = alice});
3761 auto const mpt = mptGw["MPT"];
3762 env(pay(gw, alice, mpt(10'000)));
3763 env.close();
3764
3765 uint32_t seq = 0;
3767 env,
3768 cosigning,
3769 sponsor,
3770 alice,
3771 1,
3772 1,
3774 [&](Env& env, auto const& submit) {
3775 seq = env.seq(alice);
3776 submit(
3777 escrow::create(alice, bob, mpt(100)),
3779 escrow::kCancelTime(env.now() + 100s));
3780 });
3781
3782 BEAST_EXPECT(
3783 env.le(keylet::escrow(alice, SeqProxy::rawSequence(seq)))
3784 ->getAccountID(sfSponsor) == sponsor.id());
3785
3786 if (cosigning)
3787 {
3789 }
3790 else
3791 {
3793 env.close();
3794 }
3795
3796 if (cosigning)
3797 {
3798 env(escrow::finish(bob, alice, seq),
3802 Sig(sfSponsorSignature, sponsor),
3803 Fee(baseFee * 150),
3804 Ter(tesSUCCESS));
3805 }
3806 else
3807 {
3808 env(escrow::finish(bob, alice, seq),
3812 Fee(baseFee * 150),
3813 Ter(tesSUCCESS));
3814 }
3815 env.close();
3816
3817 BEAST_EXPECT(ownerCount(env, alice) == 1);
3818 BEAST_EXPECT(ownerCount(env, bob) == 1);
3819 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
3820 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1);
3821 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
3822
3823 BEAST_EXPECT(
3824 env.le(keylet::mptoken(mptGw.issuanceID(), bob))->getAccountID(sfSponsor) ==
3825 sponsor.id());
3826 }
3827
3828 // A sponsored EscrowCreate must still verify that the source
3829 // can fund the escrow amount and stay above its own base
3830 // reserve. The sponsor covers the new object's owner
3831 // increment, but cannot cover the source's base reserve.
3832 {
3833 Env env{*this, testableAmendments()};
3834 env.fund(XRP(10000), alice, bob, sponsor);
3835 env.close();
3836
3837 // alice's balance is just above the base reserve. After
3838 // locking escrowAmount she would dip below it.
3839 adjustAccountXRPBalance(env, alice, accountReserve(env, 1) + XRP(1));
3840
3841 auto const escrowAmount = XRP(2);
3842 auto const seq = env.seq(alice);
3843
3844 if (cosigning)
3845 {
3846 env(escrow::create(alice, bob, escrowAmount),
3848 escrow::kCancelTime(env.now() + 100s),
3850 Sig(sfSponsorSignature, sponsor),
3851 Ter(tecUNFUNDED));
3852 }
3853 else
3854 {
3855 env(sponsor::set(sponsor, 0, 1, XRP(1)), sponsor::SponseeAcc(alice));
3856 env.close();
3857
3858 env(escrow::create(alice, bob, escrowAmount),
3860 escrow::kCancelTime(env.now() + 100s),
3862 Ter(tecUNFUNDED));
3863 }
3864 env.close();
3865
3866 BEAST_EXPECT(!env.le(keylet::escrow(alice, SeqProxy::rawSequence(seq))));
3867 BEAST_EXPECT(ownerCount(env, alice) == 0);
3868 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
3869 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3870 }
3871 }
3872
3873 void
3874 testMPToken(bool cosigning)
3875 {
3876 testcase("MPToken");
3877 using namespace test::jtx;
3878 Account const alice("alice");
3879 Account const bob("bob");
3880 Account const sponsor("sponsor");
3881 Account const sponsor2("sponsor2");
3882
3883 {
3884 Env env{*this, testableAmendments()};
3885 env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
3886 env.close();
3887
3888 // MPTokenIssuanceCreate
3889 json::Value jv = {};
3890 jv[sfAccount] = alice.human();
3891 jv[sfTransactionType] = jss::MPTokenIssuanceCreate;
3892 MPTID mptid;
3894 env,
3895 cosigning,
3896 sponsor,
3897 alice,
3898 1,
3899 1,
3901 [&](Env& env, auto const& submit) {
3902 mptid = makeMptID(env.seq(alice), alice.id());
3903 submit(jv);
3904 });
3905
3906 // transfer sponsor
3907 auto const mptIssuanceKeylet = keylet::mptokenIssuance(mptid);
3908
3909 if (cosigning)
3910 {
3911 env(sponsor::transfer(alice, tfSponsorshipReassign, mptIssuanceKeylet.key),
3912 sponsor::As(sponsor2, spfSponsorReserve),
3913 Sig(sfSponsorSignature, sponsor2));
3914 env.close();
3915 }
3916 else
3917 {
3918 env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice));
3919 env.close();
3920
3921 env(sponsor::transfer(alice, tfSponsorshipReassign, mptIssuanceKeylet.key),
3922 sponsor::As(sponsor2, spfSponsorReserve));
3923 env.close();
3924 }
3925
3926 // MPTokenAuthorize
3927 jv = {};
3928 jv[sfTransactionType] = jss::MPTokenAuthorize;
3929 jv[sfAccount] = bob.human();
3930 jv[sfMPTokenIssuanceID] = to_string(mptid);
3931
3932 if (cosigning)
3933 {
3935 env(ticket::create(sponsor, 2)); // adjust for free mptoken
3936 env.close();
3937 }
3938
3940 env,
3941 cosigning,
3942 sponsor,
3943 bob,
3944 1,
3945 1,
3947 [&](Env& env, auto const& submit) { submit(jv); });
3948
3949 BEAST_EXPECT(ownerCount(env, alice) == 1);
3950 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
3951
3952 // transfer sponsor
3953 auto const mptTokenKeylet = keylet::mptoken(mptid, bob);
3954 if (cosigning)
3955 {
3956 env(sponsor::transfer(bob, tfSponsorshipReassign, mptTokenKeylet.key),
3957 sponsor::As(sponsor2, spfSponsorReserve),
3958 Sig(sfSponsorSignature, sponsor2));
3959 env.close();
3960 }
3961 else
3962 {
3963 env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(bob));
3964 env.close();
3965
3966 env(sponsor::transfer(bob, tfSponsorshipReassign, mptTokenKeylet.key),
3967 sponsor::As(sponsor2, spfSponsorReserve));
3968 env.close();
3969 }
3970
3971 BEAST_EXPECT(ownerCount(env, alice) == 1);
3972 BEAST_EXPECT(ownerCount(env, bob) == 1);
3973 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
3974 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1);
3975 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
3976 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 2);
3977
3978 // MPTokenAuthorize Unauthorize
3979 jv = {};
3980 jv[sfTransactionType] = jss::MPTokenAuthorize;
3981 jv[sfAccount] = bob.human();
3982 jv[sfMPTokenIssuanceID] = to_string(mptid);
3983 jv[sfFlags] = tfMPTUnauthorize;
3984 env(jv);
3985 env.close();
3986
3987 BEAST_EXPECT(ownerCount(env, alice) == 1);
3988 BEAST_EXPECT(ownerCount(env, bob) == 0);
3989 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
3990 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
3991 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
3992
3993 // MPTokenIssuanceDestroy
3994 jv = {};
3995 jv[sfTransactionType] = jss::MPTokenIssuanceDestroy;
3996 jv[sfAccount] = alice.human();
3997 jv[sfMPTokenIssuanceID] = to_string(mptid);
3998 env(jv);
3999 env.close();
4000
4001 BEAST_EXPECT(ownerCount(env, alice) == 0);
4002 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
4003 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
4004 }
4005 {
4006 // check INSUFFICIENT_RESERVE for MPToken
4007 Env env{*this, testableAmendments()};
4008 env.fund(XRP(1000000), alice, bob, sponsor);
4009 env.close();
4010
4011 // MPTokenAuthorize
4012 json::Value jv = {};
4013 jv[sfAccount] = alice.human();
4014 jv[sfTransactionType] = jss::MPTokenIssuanceCreate;
4015 auto const mptid = makeMptID(env.seq(alice), alice.id());
4016 env(jv);
4017 env.close();
4018
4019 // Create tickets so the sponsor is past free-tier reserve behavior.
4020 std::uint32_t const ticketSeq{env.seq(sponsor) + 1};
4021 env(ticket::create(sponsor, 2));
4022 env.close();
4023
4024 jv = {};
4025 jv[sfTransactionType] = jss::MPTokenAuthorize;
4026 jv[sfAccount] = bob.human();
4027 jv[sfMPTokenIssuanceID] = to_string(mptid);
4028 // error (non-free mptoken)
4029 if (cosigning)
4030 {
4031 adjustAccountXRPBalance(env, sponsor, reserve(env, 3) - drops(1));
4032 env(jv,
4034 Sig(sfSponsorSignature, sponsor),
4036 env.close();
4037 }
4038 else
4039 {
4040 env(sponsor::set(sponsor, 0, std::nullopt, XRP(1)), sponsor::SponseeAcc(bob));
4041 env.close();
4042
4044 env.close();
4045 }
4046
4047 env(noop(sponsor), ticket::Use(ticketSeq));
4048 env.close();
4049
4050 // pass (free-tier mptoken for the holder, but the sponsor is still
4051 // charged a reserve increment regardless of the ownerCount < 2 shortcut).
4052 if (cosigning)
4053 {
4055 env(jv,
4057 Sig(sfSponsorSignature, sponsor),
4058 Ter(tesSUCCESS));
4059 env.close();
4060 }
4061 else
4062 {
4064 env.close();
4066 env.close();
4067 }
4068 }
4069 }
4070
4071 void
4072 testPayChan(bool cosigning)
4073 {
4074 testcase("PayChan");
4075 using namespace test::jtx;
4076 using namespace std::literals::chrono_literals;
4077 Account const alice("alice");
4078 Account const bob("bob");
4079 Account const sponsor("sponsor");
4080 Account const sponsor2("sponsor2");
4081
4082 {
4083 Env env{*this, testableAmendments()};
4084 env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
4085 env.close();
4086
4087 // PayChanCreate
4088 auto const pk = alice.pk();
4089 auto const settleDelay = 10s;
4090 UInt256 chan;
4092 env,
4093 cosigning,
4094 sponsor,
4095 alice,
4096 1,
4097 1,
4099 [&](Env& env, auto const& submit) {
4100 chan = paychan::channel(alice, bob, env.seq(alice));
4101 submit(paychan::create(alice, bob, XRP(100), settleDelay, pk));
4102 });
4103
4104 // the created PayChannel object carries the sponsor
4105 BEAST_EXPECT(env.le(Keylet(ltPAYCHAN, chan))->getAccountID(sfSponsor) == sponsor.id());
4106
4107 // transfer sponsor
4108 if (cosigning)
4109 {
4110 env(sponsor::transfer(alice, tfSponsorshipReassign, chan),
4111 sponsor::As(sponsor2, spfSponsorReserve),
4112 Sig(sfSponsorSignature, sponsor2));
4113 env.close();
4114 }
4115 else
4116 {
4117 env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice));
4118 env.close();
4119
4120 env(sponsor::transfer(alice, tfSponsorshipReassign, chan),
4121 sponsor::As(sponsor2, spfSponsorReserve));
4122 env.close();
4123 }
4124
4125 BEAST_EXPECT(ownerCount(env, alice) == 1);
4126 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
4127 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
4128 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
4129 BEAST_EXPECT(env.le(Keylet(ltPAYCHAN, chan))->getAccountID(sfSponsor) == sponsor2.id());
4130
4131 env.close(env.now() + settleDelay);
4132 // PayChanClaim (delete PayChan)
4133 env(paychan::claim(bob, chan), Txflags(tfClose));
4134 env.close();
4135
4136 BEAST_EXPECT(ownerCount(env, alice) == 0);
4137 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
4138 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
4139 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
4140 }
4141
4142 // A sponsored PaymentChannelCreate must still verify that the
4143 // source can fund the channel amount and stay above its own
4144 // base reserve. The sponsor covers the new object's owner
4145 // increment, but cannot cover the source's base reserve.
4146 {
4147 Env env{*this, testableAmendments()};
4148 env.fund(XRP(10000), alice, bob, sponsor);
4149 env.close();
4150
4151 // alice's balance is just above the base reserve. After
4152 // locking channelAmount she would dip below it.
4153 adjustAccountXRPBalance(env, alice, accountReserve(env, 1) + XRP(1));
4154
4155 auto const pk = alice.pk();
4156 auto const settleDelay = 10s;
4157 auto const channelAmount = XRP(2);
4158 auto const chan = paychan::channel(alice, bob, env.seq(alice));
4159
4160 if (cosigning)
4161 {
4162 env(paychan::create(alice, bob, channelAmount, settleDelay, pk),
4164 Sig(sfSponsorSignature, sponsor),
4165 Ter(tecUNFUNDED));
4166 }
4167 else
4168 {
4169 env(sponsor::set(sponsor, 0, 1, XRP(1)), sponsor::SponseeAcc(alice));
4170 env.close();
4171
4172 env(paychan::create(alice, bob, channelAmount, settleDelay, pk),
4174 Ter(tecUNFUNDED));
4175 }
4176 env.close();
4177
4178 BEAST_EXPECT(!paychan::channelExists(*env.current(), chan));
4179 BEAST_EXPECT(ownerCount(env, alice) == 0);
4180 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
4181 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
4182 }
4183 }
4184
4185 void
4186 testSignerList(bool cosigning)
4187 {
4188 testcase("SignerList");
4189 using namespace test::jtx;
4190 Account const alice("alice");
4191 Account const bob("bob");
4192 Account const sponsor("sponsor");
4193 Account const sponsor2("sponsor2");
4194
4195 Env env{*this, testableAmendments()};
4196 env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
4197 env.close();
4198
4199 // SignerListSet
4201 env,
4202 cosigning,
4203 sponsor,
4204 alice,
4205 1,
4206 1,
4208 [&](Env& env, auto const& submit) { submit(signers(alice, 1, {{bob, 1}})); });
4209
4210 // the created SignerList object carries the sponsor
4211 BEAST_EXPECT(env.le(keylet::signerList(alice))->getAccountID(sfSponsor) == sponsor.id());
4212
4213 // transfer sponsor
4214 if (cosigning)
4215 {
4216 // invalid signer list owner 1
4217 // account doesn't have signer list but specified signer list exists
4218 env(sponsor::transfer(bob, tfSponsorshipReassign, keylet::signerList(alice).key),
4219 sponsor::As(sponsor2, spfSponsorReserve),
4220 Sig(sfSponsorSignature, sponsor2),
4222 // invalid signer list owner 2
4223 // account has signer list and specified signer list exists
4224 env(signers(bob, 1, {{alice, 1}}));
4225 env.close();
4226 env(sponsor::transfer(alice, tfSponsorshipReassign, keylet::signerList(bob).key),
4227 sponsor::As(sponsor2, spfSponsorReserve),
4228 Sig(sfSponsorSignature, sponsor2),
4230 env(sponsor::transfer(alice, tfSponsorshipReassign, keylet::signerList(alice).key),
4231 sponsor::As(sponsor2, spfSponsorReserve),
4232 Sig(sfSponsorSignature, sponsor2));
4233 env.close();
4234 }
4235 else
4236 {
4237 env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice));
4238 env.close();
4239 env(sponsor::transfer(alice, tfSponsorshipReassign, keylet::signerList(alice).key),
4240 sponsor::As(sponsor2, spfSponsorReserve));
4241 env.close();
4242 }
4243
4244 BEAST_EXPECT(ownerCount(env, alice) == 1);
4245 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
4246 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
4247 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
4248 BEAST_EXPECT(env.le(keylet::signerList(alice))->getAccountID(sfSponsor) == sponsor2.id());
4249
4250 // Delete
4251 env(signers(alice, NoneT()));
4252 env.close();
4253
4254 BEAST_EXPECT(ownerCount(env, alice) == 0);
4255 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
4256 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
4257 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
4258 }
4259
4260 // Legacy (pre-MultiSignReserve) SignerLists lack lsfOneOwnerCount and cost
4261 // 2 + signer_count owner units, whereas modern lists cost 1.
4262 void
4264 {
4265 testcase("Legacy SignerList sponsorship reserve");
4266 using namespace test::jtx;
4267
4268 Account const alice("alice");
4269 Account const bob("bob");
4270 Account const carol("carol");
4271 Account const dave("dave");
4272 Account const sponsor("sponsor");
4273
4274 Env env{*this, testableAmendments()};
4275 env.fund(XRP(1000000), alice, bob, carol, dave, sponsor);
4276 env.close();
4277
4278 // Modern 3-signer list: weight 1, lsfOneOwnerCount set.
4279 env(signers(alice, 1, {{bob, 1}, {carol, 1}, {dave, 1}}));
4280 env.close();
4281
4282 auto const signerListKeylet = keylet::signerList(alice.id());
4283 auto const sponsorKeylet = keylet::sponsorship(sponsor.id(), alice.id());
4284 std::uint32_t const legacyWeight = 5; // 2 + 3 signers
4285 BEAST_EXPECT(ownerCount(env, alice) == 1);
4286
4287 // Pre-fund exactly the legacy weight
4288 env(sponsor::set_reserve(sponsor, 0, legacyWeight), sponsor::SponseeAcc(alice));
4289 env.close();
4290 if (auto const sle = env.le(sponsorKeylet); BEAST_EXPECT(sle))
4291 BEAST_EXPECT(sle->getFieldU32(sfRemainingOwnerCount) == legacyWeight);
4292
4293 // Synthesize a pre-MultiSignReserve list: clear lsfOneOwnerCount and
4294 // restore the owner's OwnerCount to the legacy weight.
4295 env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal) -> bool {
4296 auto signerList = std::make_shared<SLE>(*view.read(signerListKeylet));
4297 auto account = std::make_shared<SLE>(*view.read(keylet::account(alice.id())));
4298 signerList->clearFlag(lsfOneOwnerCount);
4299 account->setFieldU32(sfOwnerCount, legacyWeight);
4300 view.rawReplace(signerList);
4301 view.rawReplace(account);
4302 return true;
4303 });
4304 if (auto const sle = env.le(signerListKeylet); BEAST_EXPECT(sle))
4305 BEAST_EXPECT((sle->getFlags() & lsfOneOwnerCount) == 0);
4306 BEAST_EXPECT(ownerCount(env, alice) == legacyWeight);
4307
4308 // Create must charge the full legacy weight (5), not 1: the bug bumped
4309 // the counters by 1 and left 4 pre-funded units unspent.
4310 env(sponsor::transfer(alice, tfSponsorshipCreate, signerListKeylet.key),
4312
4313 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == legacyWeight);
4314 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == legacyWeight);
4315 // All pre-funded units consumed (drained to absent).
4316 if (auto const sle = env.le(sponsorKeylet); BEAST_EXPECT(sle))
4317 BEAST_EXPECT(!sle->isFieldPresent(sfRemainingOwnerCount));
4318
4319 // Delete unwinds the legacy weight; create bumped by the same amount, so
4320 // the counters return to 0 (the bug bumped by 1 -> underflow on delete).
4321 env(signers(alice, NoneT()));
4322
4323 BEAST_EXPECT(!env.le(signerListKeylet));
4324 BEAST_EXPECT(ownerCount(env, alice) == 0);
4325 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
4326 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
4327 if (auto const sle = env.le(sponsorKeylet); BEAST_EXPECT(sle))
4328 BEAST_EXPECT(!sle->isFieldPresent(sfRemainingOwnerCount));
4329 }
4330
4331 void
4333 {
4334 // An account with ownerCount < 2 may create its first trust lines even
4335 // without meeting the reserve. In any case, the sponsor pays the full
4336 // reserve in all cases, even for the sponsee's very first trust line.
4337 testcase("Sponsored trust line gets no free-reserve exception");
4338 using namespace test::jtx;
4339
4340 Account const issuer("issuer");
4341 Account const alice("alice");
4342 Account const sponsor("sponsor");
4343
4344 Env env{*this, testableAmendments()};
4345 env.fund(XRP(10000), issuer, alice, sponsor);
4346 env.close();
4347
4348 auto const usd = issuer["usd"];
4349 auto const lineKeylet = keylet::trustLine(alice, issuer, usd.currency);
4350
4351 // Sponsor funded for exactly its base reserve
4353
4354 // alice's ownerCount is 0, so an unsponsored first trust line would be
4355 // free; but because it is sponsored, the reserve check is enforced
4356 // against the sponsor, which is one increment short.
4357 env(trust(alice, usd(100)),
4359 Sig(sfSponsorSignature, sponsor),
4361 env.close();
4362
4363 BEAST_EXPECT(!env.le(lineKeylet));
4364 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
4365
4366 // Give the sponsor has exactly one owner-reserve increment; the same
4367 // sponsored first trust line now succeeds and the sponsor pays for it.
4369
4370 env(trust(alice, usd(100)),
4372 Sig(sfSponsorSignature, sponsor));
4373 env.close();
4374
4375 BEAST_EXPECT(env.le(lineKeylet));
4376 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
4377 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
4378 BEAST_EXPECT(ownerCount(env, alice) == 1);
4379 }
4380
4381 void
4383 {
4384 // sponsor co-signs, so a fee-only object (ReserveCount == 0) makes a co-signed
4385 // reserve sponsorship fail -- with no fallback to the sponsor's balance.
4386 testcase("Co-signed reserve sponsorship is bounded by Sponsorship budget");
4387 using namespace test::jtx;
4388
4389 Env env{*this, testableAmendments()};
4390 Account const sponsor("sponsor");
4391 Account const sponsee("sponsee");
4392 env.fund(XRP(10000), sponsor, sponsee);
4393 env.close();
4394
4395 // Prefund a FEE-only Sponsorship for the sponsee; ReserveCount
4396 // defaults to 0.
4397 env(sponsor::set_fee(sponsor, 0, XRP(100)), sponsor::SponseeAcc(sponsee));
4398 env.close();
4399 BEAST_EXPECT(env.le(keylet::sponsorship(sponsor, sponsee)));
4400
4401 // Sponsee creates a Check with the sponsor co-signing the reserve. The
4402 // fee-only Sponsorship's has ReserveCount (0), so this fails
4403 // with tecINSUFFICIENT_RESERVE
4404 env(check::create(sponsee, sponsor, XRP(1)),
4406 Sig(sfSponsorSignature, sponsor),
4408 env.close();
4409
4410 BEAST_EXPECT(ownerCount(env, sponsee) == 0);
4411 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
4412 BEAST_EXPECT(sponsoredOwnerCount(env, sponsee) == 0);
4413
4414 // Bumping the Sponsorship's ReserveCount budget makes the same
4415 // co-signed reserve sponsorship succeed, the budget is what gates it.
4416 env(sponsor::set_reserve(sponsor, 0, 1), sponsor::SponseeAcc(sponsee));
4417 env.close();
4418
4419 env(check::create(sponsee, sponsor, XRP(1)),
4421 Sig(sfSponsorSignature, sponsor),
4422 Ter(tesSUCCESS));
4423 env.close();
4424
4425 BEAST_EXPECT(ownerCount(env, sponsee) == 1);
4426 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
4427 BEAST_EXPECT(sponsoredOwnerCount(env, sponsee) == 1);
4428 }
4429
4430 void
4431 testTrustSet(bool cosigning)
4432 {
4433 testcase("TrustSet");
4434 using namespace test::jtx;
4435 Account const alice("alice");
4436 Account const bob("bob");
4437 Account const charlie("charlie");
4438 Account const sponsor("sponsor");
4439 Account const sponsor2("sponsor2");
4440
4441 auto const validateSponsoredTrustline =
4442 [&](std::shared_ptr<const SLE> const& sle, bool isIssuerHigh, Account const& sponsor) {
4443 BEAST_EXPECT(
4444 sle->getAccountID(isIssuerHigh ? sfLowSponsor : sfHighSponsor) == sponsor.id());
4445 BEAST_EXPECT(!sle->isFieldPresent(isIssuerHigh ? sfHighSponsor : sfLowSponsor));
4446 };
4447
4448 auto const& highAcc = alice > bob ? alice : bob;
4449 auto const& lowAcc = alice > bob ? bob : alice;
4450
4451 // create and delete
4452 for (bool const isIssuerHigh : {false, true})
4453 {
4454 Env env{*this, testableAmendments()};
4455 env.fund(XRP(1000000), alice, bob, charlie, sponsor, sponsor2);
4456 env.close();
4457
4458 auto const& issuer = isIssuerHigh ? highAcc : lowAcc;
4459 auto const& user = isIssuerHigh ? lowAcc : highAcc;
4460
4461 auto const usd = issuer["usd"];
4462 auto const currency = usd.currency;
4463
4464 // create TrustLine
4465 if (cosigning)
4466 {
4468 env(ticket::create(sponsor, 2)); // adjust for free trustline
4469 env.close();
4470 }
4471
4473 env,
4474 cosigning,
4475 sponsor,
4476 user,
4477 1,
4478 1,
4480 [&](Env& env, auto const& submit) { submit(trust(user, usd(100))); });
4481
4482 auto const keylet = keylet::trustLine(user, issuer, currency);
4483
4484 if (cosigning)
4485 {
4486 // invalid owner
4487 env(sponsor::transfer(charlie, tfSponsorshipReassign, keylet.key),
4488 sponsor::As(sponsor2, spfSponsorReserve),
4489 Sig(sfSponsorSignature, sponsor2),
4491 // invalid reserve owner
4492 env(sponsor::transfer(issuer, tfSponsorshipReassign, keylet.key),
4493 sponsor::As(sponsor2, spfSponsorReserve),
4494 Sig(sfSponsorSignature, sponsor2),
4496 env(sponsor::transfer(user, tfSponsorshipReassign, keylet.key),
4497 sponsor::As(sponsor2, spfSponsorReserve),
4498 Sig(sfSponsorSignature, sponsor2));
4499 env.close();
4500 }
4501 else
4502 {
4503 env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(user));
4504 env.close();
4505 env(sponsor::transfer(user, tfSponsorshipReassign, keylet.key),
4506 sponsor::As(sponsor2, spfSponsorReserve));
4507 env.close();
4508 }
4509
4510 // delete TrustLine
4511 env(trust(user, usd(0)));
4512 env.close();
4513
4514 BEAST_EXPECT(ownerCount(env, user) == 0);
4515 BEAST_EXPECT(sponsoredOwnerCount(env, user) == 0);
4516 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
4517
4518 BEAST_EXPECT(!env.le(keylet));
4519 }
4520
4521 // update
4522 for (bool const isIssuerHigh : {false, true})
4523 {
4524 Env env{*this, testableAmendments()};
4525 env.fund(XRP(1000000), alice, bob, sponsor, sponsor2);
4526 env.close();
4527
4528 auto const& issuer = isIssuerHigh ? highAcc : lowAcc;
4529 auto const& user = isIssuerHigh ? lowAcc : highAcc;
4530
4531 auto const usd = issuer["usd"];
4532 auto const currency = usd.currency;
4533
4534 // create TrustLine from issuer
4535 env(trust(issuer, user["usd"](100)));
4536 env.close();
4537
4538 BEAST_EXPECT(env.le(keylet::trustLine(user, issuer, currency)));
4539
4540 if (cosigning)
4541 {
4543 env(ticket::create(sponsor, 2)); // adjust for free trustline
4544 env.close();
4545 }
4546
4548 env,
4549 cosigning,
4550 sponsor,
4551 user,
4552 1,
4553 1,
4555 [&](Env& env, auto const& submit) { submit(trust(user, usd(100))); });
4556
4557 auto const line = env.le(keylet::trustLine(user, issuer, currency));
4558 validateSponsoredTrustline(line, isIssuerHigh, sponsor);
4559
4560 // update TrustLine from user to clear reserve
4561 env(trust(user, usd(0)));
4562 env.close();
4563
4564 BEAST_EXPECT(ownerCount(env, user) == 0);
4565 BEAST_EXPECT(sponsoredOwnerCount(env, user) == 0);
4566 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
4567 BEAST_EXPECT(env.le(keylet::trustLine(user, issuer, currency)));
4568
4569 // remove TrustLine from issuer
4570 env(trust(issuer, user["usd"](0)));
4571 env.close();
4572 BEAST_EXPECT(!env.le(keylet::trustLine(user, issuer, currency)));
4573 }
4574
4575 // both High and Low sponsored
4576 {
4577 Env env{*this, testableAmendments()};
4578 env.fund(XRP(1000000), alice, bob, sponsor);
4579 env.close();
4580
4581 // create TrustLines
4582 env(trust(alice, bob["usd"](100)),
4584 Sig(sfSponsorSignature, sponsor));
4585 env.close();
4586 env(trust(bob, alice["usd"](100)),
4588 Sig(sfSponsorSignature, sponsor));
4589 env.close();
4590
4591 auto sle = env.le(keylet::trustLine(alice, bob, alice["usd"].currency));
4592 BEAST_EXPECT(sle);
4593 BEAST_EXPECT(sle->isFlag(lsfHighReserve));
4594 BEAST_EXPECT(sle->isFlag(lsfLowReserve));
4595 BEAST_EXPECT(sle->getAccountID(sfHighSponsor) == sponsor.id());
4596 BEAST_EXPECT(sle->getAccountID(sfLowSponsor) == sponsor.id());
4597
4598 BEAST_EXPECT(ownerCount(env, alice) == 1);
4599 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
4600 BEAST_EXPECT(ownerCount(env, bob) == 1);
4601 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 1);
4602 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 2);
4603
4604 // clear TrustLines
4605 env(trust(alice, bob["usd"](0)));
4606 env.close();
4607 env(trust(bob, alice["usd"](0)));
4608 env.close();
4609
4610 sle = env.le(keylet::trustLine(alice, bob, alice["usd"].currency));
4611 BEAST_EXPECT(!sle);
4612 BEAST_EXPECT(ownerCount(env, alice) == 0);
4613 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
4614 BEAST_EXPECT(ownerCount(env, bob) == 0);
4615 BEAST_EXPECT(sponsoredOwnerCount(env, bob) == 0);
4616 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
4617 }
4618 }
4619
4620 void
4622 {
4623 testcase("AccountDelete");
4624 using namespace test::jtx;
4625 Account const alice("alice");
4626 Account const bob("bob");
4627 Account const sponsor("sponsor");
4628
4629 {
4630 // Delete Sponsor/Sponsee Account with ltSponsorship (tecHAS_OBLIGATIONS)
4631 Env env{*this, testableAmendments()};
4632 env.fund(XRP(1000000), alice, bob, sponsor);
4633 env.close();
4634
4635 // set sponsor
4636 env(sponsor::set(sponsor, 0, 100, XRP(100)),
4637 sponsor::SponseeAcc(alice),
4638 Ter(tesSUCCESS));
4639 env.close();
4640
4642
4643 auto const keylet = keylet::sponsorship(sponsor, alice);
4644 auto const sponsorObj = env.le(keylet);
4645 BEAST_EXPECT(sponsorObj);
4646
4647 // AccountDelete
4648 auto const requiredFee = drops(env.current()->fees().increment);
4649 env(acctdelete(alice, bob), Fee(requiredFee), Ter(tecHAS_OBLIGATIONS));
4650 env(acctdelete(sponsor, bob), Fee(requiredFee), Ter(tecHAS_OBLIGATIONS));
4651 }
4652
4653 {
4654 // Delete SponsoredAccount
4655 Env env{*this, testableAmendments()};
4656 env.memoize(alice);
4657 env.fund(XRP(1000000), bob, sponsor);
4658 env.close();
4659
4660 // create SponsoredAccount
4661 env(pay(sponsor, alice, XRP(10000)), Txflags(tfSponsorCreatedAccount));
4662 env.close();
4663
4664 incLgrSeqForAccDel(env, alice);
4665
4666 // AccountDelete: destination = non-sponsor
4667 auto const requiredFee = drops(env.current()->fees().increment);
4668 env(acctdelete(alice, bob), Fee(requiredFee), Ter(tecNO_SPONSOR_PERMISSION));
4669
4670 // The failed delete must leave the account sponsored by the original sponsor.
4671 auto const aliceSle = env.le(keylet::account(alice));
4672 BEAST_EXPECT(aliceSle);
4673 if (aliceSle)
4674 BEAST_EXPECT(aliceSle->getAccountID(sfSponsor) == sponsor.id());
4675
4676 auto const sponsorSle = env.le(keylet::account(sponsor));
4677 BEAST_EXPECT(sponsorSle->getFieldU32(sfSponsoringAccountCount) == 1);
4678
4679 incLgrSeqForAccDel(env, alice);
4680
4681 // AccountDelete: destination = sponsor
4682 env(acctdelete(alice, sponsor), Fee(requiredFee), Ter(tesSUCCESS));
4683
4684 auto const sponsorSle2 = env.le(keylet::account(sponsor));
4685 BEAST_EXPECT(!sponsorSle2->isFieldPresent(sfSponsoringAccountCount));
4686 }
4687
4688 {
4689 // Sponsor with sfSponsoringOwnerCount cannot delete (tecHAS_OBLIGATIONS)
4690 Env env{*this, testableAmendments()};
4691 Account const gw("gw");
4692 env.fund(XRP(1000000), alice, bob, sponsor, gw);
4693 env.close();
4694
4695 auto const usd = gw["usd"];
4696
4697 // Create a trust line for alice
4698 env(trust(alice, usd(1000)));
4699 env.close();
4700
4701 // Transfer reserve sponsorship of trust line to sponsor
4702 auto const trustId = keylet::trustLine(alice, gw, usd.currency);
4703 BEAST_EXPECT(env.le(trustId));
4704
4705 env(sponsor::transfer(alice, tfSponsorshipCreate, trustId.key),
4707 Sig(sfSponsorSignature, sponsor));
4708 env.close();
4709
4710 // Verify sfSponsoringOwnerCount is set on sponsor
4711 auto const sponsorSle = env.le(keylet::account(sponsor));
4712 BEAST_EXPECT(sponsorSle->isFieldPresent(sfSponsoringOwnerCount));
4713 auto const sponsoringOwnerCount = sponsorSle->getFieldU32(sfSponsoringOwnerCount);
4714 BEAST_EXPECT(sponsoringOwnerCount >= 1);
4715
4717
4718 // AccountDelete should fail
4719 auto const requiredFee = drops(env.current()->fees().increment);
4720 env(acctdelete(sponsor, bob), Fee(requiredFee), Ter(tecHAS_OBLIGATIONS));
4721 // The failed delete must not decrement the outstanding sponsored-object count.
4722 auto const sponsorSleAfter = env.le(keylet::account(sponsor));
4723 BEAST_EXPECT(sponsorSleAfter->isFieldPresent(sfSponsoringOwnerCount));
4724 BEAST_EXPECT(
4725 sponsorSleAfter->getFieldU32(sfSponsoringOwnerCount) == sponsoringOwnerCount);
4726 }
4727
4728 {
4729 // Sponsor with sfSponsoringAccountCount cannot delete (tecHAS_OBLIGATIONS)
4730 Env env{*this, testableAmendments()};
4731 env.memoize(alice);
4732 env.fund(XRP(1000000), bob, sponsor);
4733 env.close();
4734
4735 // Create SponsoredAccount (sets sfSponsoringAccountCount on sponsor)
4736 env(pay(sponsor, alice, XRP(10000)), Txflags(tfSponsorCreatedAccount));
4737 env.close();
4738
4739 // Verify sfSponsoringAccountCount is set on sponsor
4740 auto const sponsorSle = env.le(keylet::account(sponsor));
4741 BEAST_EXPECT(sponsorSle->isFieldPresent(sfSponsoringAccountCount));
4742 auto const sponsoringAccountCount = sponsorSle->getFieldU32(sfSponsoringAccountCount);
4743 BEAST_EXPECT(sponsoringAccountCount == 1);
4744
4746
4747 // AccountDelete should fail
4748 auto const requiredFee = drops(env.current()->fees().increment);
4749 env(acctdelete(sponsor, bob), Fee(requiredFee), Ter(tecHAS_OBLIGATIONS));
4750 // The failed delete must not decrement the outstanding sponsored-account count.
4751 auto const sponsorSleAfter = env.le(keylet::account(sponsor));
4752 BEAST_EXPECT(sponsorSleAfter->isFieldPresent(sfSponsoringAccountCount));
4753 BEAST_EXPECT(
4754 sponsorSleAfter->getFieldU32(sfSponsoringAccountCount) == sponsoringAccountCount);
4755 }
4756
4757 {
4758 // Account with sponsored objects should be deletable
4759 Env env{*this, testableAmendments()};
4760 env.fund(XRP(1000000), alice, bob, sponsor);
4761 env.close();
4762
4763 // Create sponsored delegate (a non-deletion-blocker)
4764 env(deposit::auth(alice, bob),
4766 Sig(sfSponsorSignature, sponsor));
4767
4768 {
4769 auto const sponsorSle = env.le(keylet::account(sponsor));
4770 auto const aliceSle = env.le(keylet::account(alice));
4771 BEAST_EXPECT(sponsorSle->at(sfSponsoringOwnerCount) == 1);
4772 BEAST_EXPECT(aliceSle->at(sfOwnerCount) == 1);
4773 BEAST_EXPECT(aliceSle->at(sfSponsoredOwnerCount) == 1);
4774 }
4775
4776 incLgrSeqForAccDel(env, alice);
4777
4778 // AccountDelete should succeed
4779 {
4780 auto const requiredFee = drops(env.current()->fees().increment);
4781 env(acctdelete(alice, bob), Fee(requiredFee), Ter(tesSUCCESS));
4782 BEAST_EXPECT(!env.le(keylet::account(alice)));
4783 auto const sponsorSle = env.le(keylet::account(sponsor));
4784 BEAST_EXPECT(sponsorSle->at(sfSponsoringOwnerCount) == 0);
4785 }
4786 }
4787 }
4788
4789 void
4791 {
4792 testcase("DelegatePermission");
4793 using namespace test::jtx;
4794 Account const alice("alice");
4795 Account const bob("bob");
4796 Account const carol("carol");
4797
4798 //
4799 // SponsorshipTransfer is not delegable.
4800 //
4801 {
4802 Env env{*this, testableAmendments()};
4803 env.fund(XRP(1000000), alice, bob, carol);
4804 env.close();
4805
4806 auto const seq = env.seq(alice);
4807 env(check::create(alice, bob, XRP(1)));
4808 env.close();
4809
4810 auto const keylet = keylet::check(alice, SeqProxy::rawSequence(seq));
4811
4812 env(sponsor::transfer(alice, tfSponsorshipCreate, keylet.key),
4814 Sig(sfSponsorSignature, bob),
4815 delegate::As(carol),
4816 Ter(temINVALID));
4817
4818 env(delegate::set(alice, carol, {"SponsorshipTransfer"}), Ter(temMALFORMED));
4819 }
4820 //
4821 // test send SponsorshipSet on other's behalf
4822 //
4823 {
4824 Env env{*this, testableAmendments()};
4825 env.fund(XRP(1000000), alice, bob, carol);
4826 env.close();
4827
4828 env(sponsor::set(alice, 0, 100, XRP(100)),
4830 delegate::As(carol),
4832
4833 env(delegate::set(alice, carol, {"SponsorshipSet"}));
4834 env.close();
4835
4836 env(sponsor::set(alice, 0, 100, XRP(100)),
4838 delegate::As(carol),
4839 Ter(tesSUCCESS));
4840 env.close();
4841 }
4842 }
4843
4844 void
4846 {
4847 testcase("Delegate Block Reserve Sponsor");
4848 using namespace test::jtx;
4849 Account const alice("alice");
4850 Account const bob("bob");
4851 Account const carol("carol");
4852 Account const sponsor("sponsor");
4853
4854 // Co-signed reserve sponsorship is blocked for delegated transactions.
4855 {
4856 Env env{*this, testableAmendments()};
4857 env.fund(XRP(1000000), alice, bob, carol, sponsor);
4858 env.close();
4859
4860 env(delegate::set(alice, bob, {"CheckCreate"}));
4861 env.close();
4862
4863 env(check::create(alice, carol, XRP(1)),
4864 delegate::As(bob),
4866 Sig(sfSponsorSignature, sponsor),
4867 Ter(temINVALID));
4868 }
4869
4870 // Pre-funded reserve sponsorship is blocked for delegated transactions.
4871 {
4872 Env env{*this, testableAmendments()};
4873 env.fund(XRP(1000000), alice, bob, carol, sponsor);
4874 env.close();
4875
4876 env(delegate::set(alice, bob, {"CheckCreate"}));
4878 env.close();
4879
4880 env(check::create(alice, carol, XRP(1)),
4881 delegate::As(bob),
4883 Ter(temINVALID));
4884 }
4885 }
4886
4887 void
4889 {
4890 testcase("Delegate Sponsor Fee Payer");
4891 using namespace test::jtx;
4892 Account const alice("alice");
4893 Account const bob("bob");
4894 Account const carol("carol");
4895 Account const sponsor("sponsor");
4896
4897 // Co-signing: the sponsor account pays the delegated transaction fee.
4898 {
4899 Env env{*this, testableAmendments()};
4900 env.fund(XRP(10000), alice, bob, carol, sponsor);
4901 env.close();
4902
4903 env(delegate::set(alice, bob, {"Payment"}));
4904 env.close();
4905
4906 auto const aliceBalance = env.balance(alice);
4907 auto const bobBalance = env.balance(bob);
4908 auto const carolBalance = env.balance(carol);
4909 auto const sponsorBalance = env.balance(sponsor);
4910 auto const sendAmt = XRP(100);
4911 auto const feeAmt = XRP(10);
4912
4913 env(pay(alice, carol, sendAmt),
4914 delegate::As(bob),
4915 Fee(feeAmt),
4917 Sig(sfSponsorSignature, sponsor));
4918 env.close();
4919
4920 BEAST_EXPECT(env.balance(alice) == aliceBalance - sendAmt);
4921 BEAST_EXPECT(env.balance(bob) == bobBalance);
4922 BEAST_EXPECT(env.balance(carol) == carolBalance + sendAmt);
4923 // sponsor pays the fee
4924 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance - feeAmt);
4925 }
4926
4927 // Pre-funded: the Sponsorship object for sponsorship(sponsor, delegate)
4928 // pays the fee.
4929 {
4930 Env env{*this, testableAmendments()};
4931 env.fund(XRP(10000), alice, bob, carol, sponsor);
4932 env.close();
4933
4934 env(delegate::set(alice, bob, {"Payment"}));
4935 env(sponsor::set_fee(sponsor, 0, XRP(100)), sponsor::SponseeAcc(bob));
4936 env.close();
4937
4938 auto const aliceBalance = env.balance(alice);
4939 auto const bobBalance = env.balance(bob);
4940 auto const carolBalance = env.balance(carol);
4941 auto const sponsorBalance = env.balance(sponsor);
4942 auto const sponsorFee = sponsor::sponsorshipFeeBalance(env, sponsor, bob);
4943 auto const sendAmt = XRP(100);
4944 auto const feeAmt = XRP(10);
4945
4946 // verify sponsorship(sponsor, alice) is not present, because we are testing
4947 // sponsorship(sponsor, bob) will pay the fee. bob is sfDelegate.
4948 BEAST_EXPECT(!env.le(keylet::sponsorship(sponsor, alice)));
4949
4950 env(pay(alice, carol, sendAmt),
4951 delegate::As(bob),
4952 Fee(feeAmt),
4954 env.close();
4955
4956 BEAST_EXPECT(env.balance(alice) == aliceBalance - sendAmt);
4957 BEAST_EXPECT(env.balance(bob) == bobBalance);
4958 BEAST_EXPECT(env.balance(carol) == carolBalance + sendAmt);
4959 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance);
4960 // sponsorship(sponsor, bob) pays the fee, bob is sfDelegate
4961 BEAST_EXPECT(sponsor::sponsorshipFeeBalance(env, sponsor, bob) == sponsorFee - feeAmt);
4962 }
4963 }
4964
4965 void
4967 {
4968 testcase("Batch");
4969 using namespace test::jtx;
4970 Account const alice("alice");
4971 Account const bob("bob");
4972 Account const sponsor("sponsor");
4973
4974 //
4975 // outer transaction
4976 //
4977 {
4978 // test outer transaction with co-signing sponsor
4979 Env env{*this, testableAmendments()};
4980 env.fund(XRP(1000), alice, bob, sponsor);
4981 env.close();
4982
4983 auto const seq = env.seq(alice);
4984 env(batch::outer(alice, seq, XRP(1), tfAllOrNothing),
4985 batch::Inner(noop(alice), seq + 1),
4986 batch::Inner(ticket::create(alice, 1), seq + 2),
4988 Sig(sfSponsorSignature, sponsor),
4989 Ter(tesSUCCESS));
4990 env.close();
4991
4992 // does not affect reserve
4993 BEAST_EXPECT(ownerCount(env, alice) == 1);
4994 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
4995 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
4996
4997 // Fee is paid by sponsor
4998 BEAST_EXPECT(env.balance(alice) == XRP(1000));
4999 BEAST_EXPECT(env.balance(sponsor) == XRP(1000 - 1));
5000 }
5001 {
5002 Env env{*this, testableAmendments()};
5003 env.fund(XRP(1000), alice, bob, sponsor);
5004 env.close();
5005
5006 // spfSponsorReserve on outer Batch is rejected
5007 for (auto const flags : {spfSponsorReserve | spfSponsorFee, spfSponsorReserve})
5008 {
5009 auto const seq = env.seq(alice);
5010 env(batch::outer(alice, seq, XRP(1), tfAllOrNothing),
5011 batch::Inner(noop(alice), seq + 1),
5012 batch::Inner(noop(alice), seq + 2),
5013 sponsor::As(sponsor, flags),
5014 Sig(sfSponsorSignature, sponsor),
5016 env.close();
5017 }
5018 }
5019 {
5020 // test outer transaction with prefunded sponsor
5021 Env env{*this, testableAmendments()};
5022 env.fund(XRP(1000), alice, bob);
5023 env.fund(XRP(1001), sponsor);
5024 env.close();
5025
5026 env(sponsor::set(sponsor, 0, 100, XRP(100)),
5027 sponsor::SponseeAcc(alice),
5028 Fee(XRP(1)),
5029 Ter(tesSUCCESS));
5030 env.close();
5031
5032 auto const seq = env.seq(alice);
5033 env(batch::outer(alice, seq, XRP(1), tfAllOrNothing),
5034 batch::Inner(noop(alice), seq + 1),
5035 batch::Inner(ticket::create(alice, 1), seq + 2),
5037 Ter(tesSUCCESS));
5038 env.close();
5039
5040 // does not affect reserve
5041 BEAST_EXPECT(ownerCount(env, alice) == 1);
5042 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
5043 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
5044
5045 // Fee is paid by sponsor object
5046 BEAST_EXPECT(env.balance(alice) == XRP(1000));
5047 BEAST_EXPECT(env.balance(sponsor) == XRP(900));
5048
5049 auto const sponsorshipSle = env.le(keylet::sponsorship(sponsor, alice));
5050 BEAST_EXPECT(sponsorshipSle);
5051 BEAST_EXPECT(sponsorshipSle->at(sfFeeAmount) == XRP(100 - 1));
5052 BEAST_EXPECT(sponsorshipSle->at(sfRemainingOwnerCount) == 100);
5053 }
5054 //
5055 // Inner transaction
5056 //
5057 {
5058 // test invalid Inner transaction with co-signing sponsor
5059 Account const signerAccount("signer");
5060 Env env{*this, testableAmendments()};
5061 env.fund(XRP(1000), alice, bob, sponsor, signerAccount);
5062 env.close();
5063
5064 env(signers(sponsor, 1, {Signer(signerAccount, 1)}));
5065 env.close();
5066
5067 {
5068 auto jt = env.jtnofill(
5069 noop(alice),
5071 Sig(sfSponsorSignature, sponsor));
5072 jt.jv.removeMember(sfTxnSignature.jsonName);
5073
5074 auto const seq = env.seq(alice);
5075 // should fail because Inner transaction cannot include SponsorSignature with
5076 // TxnSignature
5077 BEAST_EXPECT(jt.jv[sfSponsorSignature.jsonName].isMember(sfTxnSignature.jsonName));
5078 env(batch::outer(alice, seq, XRP(1), tfAllOrNothing),
5079 batch::Inner(jt.jv, seq + 1),
5080 batch::Inner(ticket::create(alice, 1), seq + 2),
5082 }
5083
5084 {
5085 auto jt = env.jtnofill(
5086 noop(alice),
5088 Msig(sfSponsorSignature, sponsor, signerAccount));
5089 jt.jv.removeMember(sfTxnSignature.jsonName);
5090
5091 auto const seq = env.seq(alice);
5092 // should fail because Inner transaction cannot include SponsorSignature with
5093 // Signers
5094 BEAST_EXPECT(jt.jv[sfSponsorSignature.jsonName].isMember(sfSigners.jsonName));
5095 env(batch::outer(alice, seq, XRP(1), tfAllOrNothing),
5096 batch::Inner(jt.jv, seq + 1),
5097 batch::Inner(ticket::create(alice, 1), seq + 2),
5099 }
5100
5101 {
5102 auto jt = env.jtnofill(
5103 noop(alice),
5105 Sig(sfSponsorSignature, sponsor));
5106 jt.jv.removeMember(sfTxnSignature.jsonName);
5107 jt.jv[sfSponsorSignature.jsonName].removeMember(sfTxnSignature.jsonName);
5108 jt.jv[sfSponsorSignature.jsonName][sfSigningPubKey.jsonName] = "";
5109
5110 auto const seq = env.seq(alice);
5111 // should fail because BatchSigners does not have signer for SponsorSignature
5112 env(batch::outer(alice, seq, XRP(1), tfAllOrNothing),
5113 batch::Inner(jt.jv, seq + 1),
5114 batch::Inner(ticket::create(alice, 1), seq + 2),
5116 }
5117 }
5118
5119 {
5120 // test Inner transaction with prefunded sponsor
5121 Env env{*this, testableAmendments()};
5122 env.fund(XRP(1000), alice, bob);
5123 env.fund(XRP(1001), sponsor);
5124 env.close();
5125
5126 env(sponsor::set(sponsor, 0, 100, XRP(100)),
5127 sponsor::SponseeAcc(alice),
5128 Fee(XRP(1)),
5129 Ter(tesSUCCESS));
5130 env.close();
5131 BEAST_EXPECT(env.balance(sponsor) == XRP(900));
5132
5133 auto jt = env.jtnofill(
5135 // remove txn signature since it is filled by env.jtnofill()
5136 jt.jv.removeMember(jss::TxnSignature);
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),
5142 Ter(tesSUCCESS));
5143 env.close();
5144
5145 // affect sponsor reserve
5146 BEAST_EXPECT(ownerCount(env, alice) == 1);
5147 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
5148 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
5149
5150 // Fee is paid by outer transaction originator (alice)
5151 BEAST_EXPECT(env.balance(alice) == XRP(999));
5152 BEAST_EXPECT(env.balance(sponsor) == XRP(900));
5153
5154 // reserve count is decreased
5155 auto const sponsorshipSle = env.le(keylet::sponsorship(sponsor, alice));
5156 BEAST_EXPECT(sponsorshipSle);
5157 BEAST_EXPECT(sponsorshipSle->at(sfFeeAmount) == XRP(100));
5158 BEAST_EXPECT(sponsorshipSle->at(sfRemainingOwnerCount) == 99);
5159 }
5160
5161 {
5162 // test Inner transaction with co-signing sponsor
5163 Env env{*this, testableAmendments()};
5164 env.fund(XRP(1000), alice, bob, sponsor);
5165 env.close();
5166
5167 auto jt = env.jtnofill(
5168 check::create(alice, bob, XRP(1)),
5170 Sig(sfSponsorSignature, sponsor));
5171 // remove txn signature since it is filled by env.jtnofill()
5172 jt.jv.removeMember(sfTxnSignature.jsonName);
5173 jt.jv[sfSponsorSignature.jsonName].removeMember(sfTxnSignature.jsonName);
5174 jt.jv[sfSponsorSignature.jsonName][sfSigningPubKey.jsonName] = "";
5175
5176 auto const seq = env.seq(alice);
5177 env(batch::outer(alice, seq, XRP(1), tfAllOrNothing),
5178 batch::Inner(noop(alice), seq + 1),
5179 batch::Inner(jt.jv, seq + 2),
5181 Ter(tesSUCCESS));
5182 env.close();
5183
5184 // affect sponsor reserve
5185 BEAST_EXPECT(ownerCount(env, alice) == 1);
5186 BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
5187 BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
5188
5189 // Fee is paid by outer transaction originator (alice)
5190 BEAST_EXPECT(env.balance(alice) == XRP(999));
5191 BEAST_EXPECT(env.balance(sponsor) == XRP(1000));
5192 }
5193
5194 // Inner tx with sfSponsor + spfSponsorFee (pre-funded) is rejected
5195 {
5196 Env env{*this, testableAmendments()};
5197 env.fund(XRP(1000), alice, bob, sponsor);
5198 env.close();
5199
5200 // Create pre-funded sponsorship
5201 env(sponsor::set_fee(sponsor, 0, XRP(1)), sponsor::SponseeAcc(alice), Fee(XRP(1)));
5202 env.close();
5203
5204 auto const seq = env.seq(alice);
5205 auto const batchFee = batch::calcBatchFee(env, 0, 2);
5206
5207 // Create inner transaction with sponsor fee
5208 auto innerPay = pay(alice, bob, XRP(1));
5209 innerPay[sfSponsor.jsonName] = sponsor.human();
5210 innerPay[sfSponsorFlags.jsonName] = static_cast<std::uint32_t>(spfSponsorFee);
5211
5212 // Should be rejected with temBAD_FEE
5213 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
5214 batch::Inner(innerPay, seq + 1),
5215 batch::Inner(noop(alice), seq + 2),
5217 env.close();
5218 }
5219
5220 // Inner tx with sfSponsor + spfSponsorFee (co-signed) is rejected
5221 {
5222 Env env{*this, testableAmendments()};
5223 env.fund(XRP(1000), alice, bob, sponsor);
5224 env.close();
5225
5226 auto const seq = env.seq(alice);
5227 auto const batchFee = batch::calcBatchFee(env, 1, 2);
5228
5229 auto innerPay = pay(alice, bob, XRP(1));
5230 innerPay[sfSponsor.jsonName] = sponsor.human();
5231 innerPay[sfSponsorFlags.jsonName] = static_cast<std::uint32_t>(spfSponsorFee);
5232
5233 // Should be rejected with temBAD_FEE
5234 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
5235 batch::Inner(innerPay, seq + 1),
5236 batch::Inner(noop(alice), seq + 2),
5239 env.close();
5240 }
5241
5242 // Inner tx with spfSponsorFee + spfSponsorReserve is rejected
5243 {
5244 Env env{*this, testableAmendments()};
5245 env.fund(XRP(1000), alice, bob, sponsor);
5246 env.close();
5247
5248 auto const seq = env.seq(alice);
5249 auto const batchFee = batch::calcBatchFee(env, 0, 2);
5250
5251 auto innerTx = check::create(alice, bob, XRP(1));
5252 innerTx[sfSponsor.jsonName] = sponsor.human();
5253 innerTx[sfSponsorFlags.jsonName] =
5255
5256 // Should be rejected with temBAD_FEE (fee sponsorship check comes first)
5257 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
5258 batch::Inner(innerTx, seq + 1),
5259 batch::Inner(noop(alice), seq + 2),
5261 env.close();
5262 }
5263
5264 // Outer batch tx with sponsor fee is allowed
5265 {
5266 Env env{*this, testableAmendments()};
5267 env.fund(XRP(1000), alice, bob, sponsor);
5268 env.close();
5269
5270 auto const seq = env.seq(bob);
5271 auto const batchFee = batch::calcBatchFee(env, 0, 2);
5272
5273 // Outer batch with sponsor fee should work fine
5274 env(batch::outer(bob, seq, batchFee, tfAllOrNothing),
5275 batch::Inner(noop(bob), seq + 1),
5276 batch::Inner(noop(bob), seq + 2),
5278 Sig(sfSponsorSignature, sponsor),
5279 Ter(tesSUCCESS));
5280 env.close();
5281
5282 // Sponsor paid the fee
5283 BEAST_EXPECT(env.balance(bob) == XRP(1000));
5284 }
5285 }
5286
5287 // Verify that the central allow-list in preflight1Sponsor rejects
5288 // spfSponsorReserve for transaction types that v1 does not permit.
5289 void
5291 {
5292 testcase("Reserve sponsor allow-list gate");
5293 using namespace test::jtx;
5294
5295 Env env{*this, testableAmendments()};
5296 Account const alice("alice");
5297 Account const bob("bob");
5298 Account const sponsor("sponsor");
5299 env.fund(XRP(10000), alice, bob, sponsor);
5300 env.close();
5301
5302 env(sponsor::set(sponsor, 0, 10, XRP(10)), sponsor::SponseeAcc(alice));
5303 env.close();
5304
5305 auto checkBlocked = [&](json::Value const& jv) {
5306 env(jv,
5308 Sig(sfSponsorSignature, sponsor),
5310 };
5311
5312 checkBlocked(ticket::create(alice, 1));
5313 checkBlocked(offer(alice, XRP(100), bob["USD"](100)));
5314 checkBlocked(did::setValid(alice));
5315 checkBlocked(token::mint(alice, 0u));
5316 checkBlocked(sponsor::set(alice, 0, 10, XRP(10)));
5317 checkBlocked(acctdelete(alice, bob));
5318 checkBlocked(loan::set(alice, UInt256(1), Number{1}));
5319 }
5320
5321 void
5322 testSponsorReserve(bool cosigning)
5323 {
5325 testSponsorReserveSimple(cosigning);
5326 testCheck(cosigning);
5327 testCredential(cosigning);
5328 testDelegate(cosigning);
5329 testDepositPreauth(cosigning);
5330 testEscrow(cosigning);
5331 testMPToken(cosigning);
5332 testPayChan(cosigning);
5333 testSignerList(cosigning);
5334 testTrustSet(cosigning);
5335 }
5336
5337 void
5339 {
5340 // Zero-balance sponsored Payment: getFeePayer() consistency check
5341 testcase("Sponsored Payment: minimal-balance account with sponsor-pays-fee");
5342
5343 using namespace jtx;
5344 Env env{*this, testableAmendments()};
5345 Account const alice("alice");
5346 Account const sponsor("sponsor");
5347 Account const dest("dest");
5348
5349 auto const baseFee = env.current()->fees().base;
5350 auto const baseReserve = env.current()->fees().reserve;
5351
5352 // Fund sponsor and dest generously, alice with base reserve + 1 XRP for payment
5353 env.fund(XRP(10000), sponsor, dest);
5354 env.fund(baseReserve + XRP(1), alice);
5355 env.close();
5356
5357 // Precondition: alice has base reserve + 1 XRP (enough for payment but not fee)
5358 BEAST_EXPECT(env.balance(alice) == baseReserve + XRP(1));
5359
5360 // Alice tries to send a Payment to dest where sponsor pays the fee via spfSponsorFee.
5361 // Passed even alice balance doesn't have enough to pay fee.
5362 auto const preDest = env.balance(dest);
5363 auto const preSponsor = env.balance(sponsor);
5364
5365 // Alice sends 1 XRP to dest, sponsor pays the fee
5366 env(pay(alice, dest, XRP(1)),
5368 Sig(sfSponsorSignature, sponsor),
5369 Fee(baseFee));
5370 env.close();
5371
5372 // Payment succeeded
5373 // Alice's balance decreased by 1 XRP (the payment amount, NOT the fee)
5374 BEAST_EXPECT(env.balance(alice) == baseReserve);
5375
5376 // Dest received 1 XRP
5377 BEAST_EXPECT(env.balance(dest) == preDest + XRP(1));
5378
5379 // Sponsor paid the fee (NOT alice)
5380 BEAST_EXPECT(env.balance(sponsor) == preSponsor - baseFee);
5381 }
5382
5383 void
5385 {
5386 // TrustSet's modify path applies the tx-level reserve sponsor to whichever
5387 // side has its reserve gate trip on this update, regardless of whether that
5388 // side belongs to the tx submitter. trustCreate only sets the submitter's
5389 // reserve flag and snapshots the counterparty's asfDefaultRipple state into
5390 // the line's NoRipple bit; if the counterparty later toggles asfDefaultRipple
5391 // (the canonical issuer flow), the line and account flags disagree and on
5392 // the submitter's next TrustSet the counterparty-side gate fires. Sponsor still will be
5393 // checked if it can be applied to that end of the trustLine.
5394
5395 testcase("TrustSet modify with sponsor does not misroute onto counterparty side");
5396
5397 using namespace test::jtx;
5398
5399 Env env{*this, testableAmendments()};
5400 Account const alice{"alice_t2178"};
5401 Account const bob{"bob_t2178"};
5402 Account const carol{"carol_t2178"};
5403
5404 // Fund without auto-setting asfDefaultRipple
5405 env.fund(XRP(100'000), alice, bob, carol);
5406 env.close();
5407
5408 // Determine account ordering
5409 bool const aliceIsHigh = alice.id() > bob.id();
5410
5411 // To trigger the bug, we need the COUNTERPARTY's reserve gate to trip
5412 // We use issuer/holder terminology where:
5413 // - holder creates the trust line (their reserve is set first)
5414 // - issuer enables DefaultRipple after (creates flag mismatch)
5415 // - holder's second TrustSet triggers issuer's reserve gate
5416
5417 auto const issuer = aliceIsHigh ? bob : alice;
5418 auto const holder = aliceIsHigh ? alice : bob;
5419 auto const usd = issuer["USD"];
5420
5421 // Issuer must NOT have DefaultRipple set initially
5422 // Clear it explicitly (env.fund may have set it)
5423 env(fclear(issuer, asfDefaultRipple));
5424 env.close();
5425
5426 // Holder creates the trust line first (holder's reserve flag is set)
5427 // At this point, issuer does NOT have DefaultRipple set, so
5428 // the NoRipple bit on issuer's side is set according to issuer's current flag
5429 env(trust(holder, usd(1'000)));
5430 env.close();
5431
5432 // Issuer now enables asfDefaultRipple (canonical issuer flow)
5433 // This creates a mismatch: issuer's account flag says DefaultRipple=true
5434 // but the trust line's NoRipple bit on issuer's side is still set
5435 env(fset(issuer, asfDefaultRipple));
5436 env.close();
5437
5438 SF_ACCOUNT const& issuerSponsorField = aliceIsHigh ? sfLowSponsor : sfHighSponsor;
5439 SF_ACCOUNT const& holderSponsorField = aliceIsHigh ? sfHighSponsor : sfLowSponsor;
5440
5441 auto const lineKey = keylet::trustLine(alice, bob, usd.currency);
5442 auto const sleLineBefore = env.le(lineKey);
5443 if (!BEAST_EXPECT(sleLineBefore))
5444 return;
5445 BEAST_EXPECT(!sleLineBefore->isFieldPresent(sfLowSponsor));
5446 BEAST_EXPECT(!sleLineBefore->isFieldPresent(sfHighSponsor));
5447
5448 auto const carolBefore = sponsoringOwnerCount(env, carol);
5449 BEAST_EXPECT(carolBefore == 0);
5450 auto const issuerSponsoredBefore = sponsoredOwnerCount(env, issuer);
5451 BEAST_EXPECT(issuerSponsoredBefore == 0);
5452
5453 // Holder modifies the trust line with Carol as sponsor
5454 // This should trigger the issuer's reserve gate because of the DefaultRipple mismatch
5455 // Carol (sponsor) should NOT be applied to issuer's side (issuer != tx submitter)
5456 env(trust(holder, usd(2'000)),
5458 Sig(sfSponsorSignature, carol),
5459 Ter(tesSUCCESS));
5460 env.close();
5461
5462 auto const sleLineAfter = env.le(lineKey);
5463 if (!BEAST_EXPECT(sleLineAfter))
5464 return;
5465
5466 // Carol only agreed to back the holder, not the issuer
5467 BEAST_EXPECT(!sleLineAfter->isFieldPresent(issuerSponsorField));
5468
5469 // Holder's side also has no sponsor because holder's reserve flag was
5470 // already set on the FIRST TrustSet (no sponsor in scope then)
5471 BEAST_EXPECT(!sleLineAfter->isFieldPresent(holderSponsorField));
5472
5473 // Carol's sponsoring count should remain unchanged (no misroute)
5474 auto const carolAfter = sponsoringOwnerCount(env, carol);
5475 BEAST_EXPECT(carolAfter == carolBefore);
5476
5477 // Issuer's sponsored count should remain unchanged (no misroute)
5478 auto const issuerSponsoredAfter = sponsoredOwnerCount(env, issuer);
5479 BEAST_EXPECT(issuerSponsoredAfter == issuerSponsoredBefore);
5480 }
5481
5482 void
5484 {
5485 testcase("Self-escrow finish reserve order gated by amendment");
5486 using namespace test::jtx;
5487 using namespace std::chrono_literals;
5488
5489 // Finishing a self-escrow (source == destination) whose trust line was
5490 // deleted while the escrow was outstanding auto-creates the line. With
5491 // the source's balance in the one-increment window
5492 // [reserve(1), reserve(2)), the finish succeeds only when the escrow
5493 // reserve is released before delivery, which either featureSponsor or
5494 // fixCleanup3_4_0 does.
5495 auto runTest = [&](FeatureBitset features, TER expected) {
5496 Account const alice("alice");
5497 Account const gw("gw");
5498 auto const usd = gw["usd"];
5499
5500 Env env{*this, features};
5501 auto const baseFee = env.current()->fees().base;
5502
5503 env.fund(XRP(10000), alice, gw);
5504 env.close();
5505
5506 env(fset(gw, asfAllowTrustLineLocking));
5507 env.close();
5508
5509 env.trust(usd(1000), alice);
5510 env.close();
5511 env(pay(gw, alice, usd(100)));
5512 env.close();
5513
5514 // Escrow alice's entire USD balance to herself. The escrowed
5515 // IOUs return to the issuer, zeroing the line balance.
5516 auto const seq = env.seq(alice);
5517 env(escrow::create(alice, alice, usd(100)),
5519 escrow::kCancelTime(env.now() + 100s));
5520 env.close();
5521
5522 // Delete the now-empty trust line. Both accounts have
5523 // DefaultRipple set (jtx fund does that), so a plain limit-0
5524 // TrustSet returns the line to its default state.
5525 env(trust(alice, usd(0)));
5526 env.close();
5527 BEAST_EXPECT(!env.le(keylet::trustLine(alice, gw, usd.currency)));
5528 BEAST_EXPECT(ownerCount(env, alice) == 1); // just the escrow
5529
5530 // Put alice's balance in the window. Pay the excess away
5531 // directly: adjustAccountXRPBalance needs the Sponsor amendment.
5532 STAmount const target = reserve(env, 1) + XRP(1);
5533 env(pay(alice, env.master, env.balance(alice) - target - baseFee), Fee(baseFee));
5534 env.close();
5535 BEAST_EXPECT(env.balance(alice) == target);
5536
5537 env(escrow::finish(alice, alice, seq),
5540 Fee(baseFee * 150),
5541 Ter(expected));
5542 env.close();
5543
5544 if (expected == tesSUCCESS)
5545 {
5546 BEAST_EXPECT(!env.le(keylet::escrow(alice, SeqProxy::rawSequence(seq))));
5547 BEAST_EXPECT(env.le(keylet::trustLine(alice, gw, usd.currency)));
5548 BEAST_EXPECT(env.balance(alice, usd) == usd(100));
5549 BEAST_EXPECT(ownerCount(env, alice) == 1); // the new line
5550 }
5551 else
5552 {
5553 BEAST_EXPECT(env.le(keylet::escrow(alice, SeqProxy::rawSequence(seq))));
5554 BEAST_EXPECT(!env.le(keylet::trustLine(alice, gw, usd.currency)));
5555 BEAST_EXPECT(ownerCount(env, alice) == 1); // still the escrow
5556 }
5557 };
5558
5559 runTest(testableAmendments() - featureSponsor - fixCleanup3_4_0, tecNO_LINE_INSUF_RESERVE);
5560 runTest(testableAmendments() - featureSponsor, tesSUCCESS);
5561 runTest(testableAmendments() - fixCleanup3_4_0, tesSUCCESS);
5562 runTest(testableAmendments(), tesSUCCESS);
5563 }
5564
5565 void
5567 {
5568 // The ValidVault invariant checks that the vault's balance and the
5569 // depositor's balance change by equal amounts. For XRP vaults it adds the
5570 // fee back into the depositor's balance change: normally the depositor
5571 // pays the fee, so their balance drops by (deposit amount + fee), and
5572 // adding the fee back leaves just the deposit amount to compare against
5573 // the vault. But when a fee sponsor pays, the depositor's balance drops
5574 // by only the deposit amount, so the fee must NOT be added back or the
5575 // equal-amount check fails.
5576 testcase("Fee-sponsored VaultDeposit/VaultWithdraw pass ValidVault invariant");
5577 using namespace test::jtx;
5578
5579 Env env{*this, testableAmendments()};
5580 Account const alice("alice");
5581 Account const sponsor("sponsor");
5582 env.fund(XRP(10000), alice, sponsor);
5583 env.close();
5584
5585 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
5586 Vault const vault{env};
5587 auto [vaultTx, vaultKeylet] = vault.create({.owner = alice, .asset = xrpAsset});
5588 env(vaultTx);
5589 env.close();
5590
5591 // Control: the same deposit shape, unsponsored, succeeds.
5592 env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = xrpAsset(100)}),
5593 Ter(tesSUCCESS));
5594 env.close();
5595
5596 // Fee-sponsored (co-signed) deposit succeeds.
5597 env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = xrpAsset(100)}),
5598 Fee(XRP(1)),
5600 Sig(sfSponsorSignature, sponsor),
5601 Ter(tesSUCCESS));
5602 env.close();
5603
5604 // The same fee-correction logic (ValidVault::deltaAssetsForParty)
5605 // drives the withdraw path, so a fee-sponsored withdrawal back to
5606 // the depositor's own account also passes on the destination side.
5607 env(vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = xrpAsset(50)}),
5608 Fee(XRP(1)),
5610 Sig(sfSponsorSignature, sponsor),
5611 Ter(tesSUCCESS));
5612 env.close();
5613 }
5614
5615 void
5617 {
5618 // Deleting a co-signed reserve-sponsored object must
5619 // refund the sponsor's SponsoringOwnerCount back to zero.
5620 testcase("Sponsored object deletion refunds sponsor owner count");
5621 using namespace test::jtx;
5622
5623 Env env{*this, testableAmendments()};
5624 Account const sponsor("sponsor");
5625 env.fund(XRP(100000), sponsor);
5626 env.close();
5627
5628 // Sponsored Check deletion - verify decreaseOwnerCountForObject
5629 // (used by CheckCancel) reads the object's sfSponsor field and refunds
5630 // the sponsor's owner count.
5631 {
5632 testcase(" — Check deletion");
5633
5634 Account const checkOwner("check_owner");
5635 Account const dest("check_dest");
5636 env.fund(XRP(100000), checkOwner, dest);
5637 env.close();
5638
5639 // Create a check with co-signed reserve sponsorship. This bumps the
5640 // sponsor's sfSponsoringOwnerCount rather than the check owner's.
5641 auto const checkSeq = env.seq(checkOwner);
5642 env(check::create(checkOwner, dest, XRP(1)),
5644 Sig(sfSponsorSignature, sponsor));
5645 env.close();
5646
5647 auto sponsorCountBefore = sponsoringOwnerCount(env, sponsor);
5648 BEAST_EXPECT(sponsorCountBefore == 1); // check costs 1 owner count
5649
5650 // Cancel (delete) the check.
5651 env(check::cancel(
5652 checkOwner, keylet::check(checkOwner, SeqProxy::rawSequence(checkSeq)).key));
5653 env.close();
5654
5655 auto sponsorCountAfter = sponsoringOwnerCount(env, sponsor);
5656 BEAST_EXPECT(sponsorCountAfter == 0); // fully refunded
5657 }
5658
5659 // Sponsored TrustSet (trust line) deletion - verify the sponsor
5660 // refund works when a sponsored trust line is deleted
5661 {
5662 testcase(" — TrustLine deletion");
5663
5664 Account const issuer("issuer");
5665 Account const holder("holder");
5666 env.fund(XRP(100000), issuer, holder);
5667 env.close();
5668
5669 auto const usd = issuer["USD"];
5670
5671 // Create trust line with sponsorship on the holder side
5672 env(trust(holder, usd(1000)),
5674 Sig(sfSponsorSignature, sponsor));
5675 env.close();
5676
5677 auto sponsorCountBefore = sponsoringOwnerCount(env, sponsor);
5678 BEAST_EXPECT(sponsorCountBefore == 1); // trust line costs 1
5679
5680 // Delete the trust line by clearing it to default
5681 env(trust(holder, usd(0)));
5682 env.close();
5683
5684 auto sponsorCountAfter = sponsoringOwnerCount(env, sponsor);
5685 BEAST_EXPECT(sponsorCountAfter == 0); // fully refunded
5686 }
5687 }
5688
5689protected:
5690 void
5738
5739 void
5740 testTxSponsor(bool cosigning)
5741 {
5742 testSponsorReserve(cosigning);
5743 }
5744
5745public:
5746 void
5747 run() override
5748 {
5749 testSponsor();
5750 }
5751};
5752
5754{
5755 void
5756 run() override
5757 {
5758 testTxSponsor(true);
5759 }
5760};
5761
5763{
5764 void
5765 run() override
5766 {
5767 testTxSponsor(false);
5768 }
5769};
5770
5772BEAST_DEFINE_TESTSUITE(SponsorTxCosigning, app, xrpl);
5773BEAST_DEFINE_TESTSUITE(SponsorTxPrefunded, app, xrpl);
5774
5775} // 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(ModifyType const &f)
Modify the open ledger.
Writable ledger view that accumulates state and tx changes.
Definition OpenView.h:59
void rawReplace(SLE::Ref sle) override
Unconditionally replace a state item.
Definition OpenView.cpp:244
SLE::const_pointer read(Keylet const &k) const override
Return the state item associated with a key.
Definition OpenView.cpp:168
Rules const & rules() const override
Returns the tx processing rules.
Definition OpenView.cpp:149
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 testTransferSponsor(FeatureBitset features)
void testSponsorReserveSimple(bool cosigning)
void run() override
Runs the suite.
void testCredential(bool cosigning)
void testSingleSigning(FeatureBitset features)
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 testMultiSigning(FeatureBitset features)
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:723
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:737
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:512
static json::Value authorizeJV(MPTAuthorize const &arg=MPTAuthorize{})
Definition mpt.cpp:440
void create(MPTCreate const &arg=MPTCreate{}, std::source_location const &loc=std::source_location::current())
Definition mpt.cpp:343
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:91
Sets the Batch transaction signers on a JTx.
Definition batch.h:142
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:232
Keylet escrow(AccountID const &src, SeqProxy const &seq) noexcept
An escrow entry.
Definition Indexes.cpp:418
Keylet did(AccountID const &account) noexcept
Definition Indexes.cpp:555
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition Indexes.cpp:374
Keylet const & feeSettings() noexcept
The (fixed) index of the object containing the ledger fees.
Definition Indexes.cpp:255
Keylet signerList(AccountID const &account) noexcept
A SignerList.
Definition Indexes.cpp:348
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:403
Keylet check(AccountID const &id, SeqProxy const &seq) noexcept
A Check.
Definition Indexes.cpp:360
Keylet unchecked(UInt256 const &key) noexcept
Any ledger entry.
Definition Indexes.cpp:397
Keylet ticket(AccountID const &id, SeqProxy const &ticketSeq)
A ticket belonging to an account.
Definition Indexes.cpp:332
Keylet nftokenPageMax(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition Indexes.cpp:438
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:573
Keylet delegate(AccountID const &account, AccountID const &authorizedAccount) noexcept
A keylet for Delegate object.
Definition Indexes.cpp:511
Keylet loanBroker(AccountID const &owner, SeqProxy const &seq) noexcept
Definition Indexes.cpp:597
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:220
Keylet sponsorship(AccountID const &sponsor, AccountID const &sponsee) noexcept
A Sponsorship.
Definition Indexes.cpp:354
Keylet loan(UInt256 const &loanBrokerID, SeqProxy const &loanSeq) noexcept
Definition Indexes.cpp:603
Keylet mptokenIssuance(MPTID const &issuanceID) noexcept
Definition Indexes.cpp:567
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:275
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:55
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:37
json::Value cancel(jtx::Account const &dest, UInt256 const &checkId)
Cancel a check.
Definition check.cpp:39
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 create(A const &account, A const &dest, STAmount const &sendMax)
Create a check.
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 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)
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)
bool channelExists(ReadView const &view, UInt256 const &chan)
UInt256 channel(AccountID const &account, AccountID const &dst, std::uint32_t seqProxyValue)
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:465
@ telINSUF_FEE_P
Definition TER.h:43
@ telENV_RPC_FAILED
Definition TER.h:54
@ terNO_PERMISSION
Definition TER.h:233
@ terINSUF_FEE_B
Definition TER.h:217
@ terNO_DELEGATE_PERMISSION
Definition TER.h:231
@ terNO_ACCOUNT
Definition TER.h:218
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:181
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:13
@ tefNOT_MULTI_SIGNING
Definition TER.h:176
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:657
BaseUInt< 256 > UInt256
Definition base_uint.h:580
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:206
@ 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:683
TERSubset< CanCvtToTER > TER
Definition TER.h:654
@ tecPSEUDO_ACCOUNT
Definition TER.h:370
@ tecNO_LINE_INSUF_RESERVE
Definition TER.h:300
@ tecINSUF_RESERVE_LINE
Definition TER.h:296
@ tecINSUFF_FEE
Definition TER.h:310
@ tecUNFUNDED_PAYMENT
Definition TER.h:293
@ tecNO_ENTRY
Definition TER.h:314
@ tecNO_SPONSOR_PERMISSION
Definition TER.h:377
@ tecFAILED_PROCESSING
Definition TER.h:294
@ tecINSUFFICIENT_RESERVE
Definition TER.h:315
@ tecLIMIT_EXCEEDED
Definition TER.h:369
@ tecNO_PERMISSION
Definition TER.h:313
@ tecHAS_OBLIGATIONS
Definition TER.h:325
@ tecNO_DST
Definition TER.h:298
@ tecUNFUNDED
Definition TER.h:303
TypedField< STAccount > SF_ACCOUNT
Definition SField.h:357
ApplyResult doApply(PreclaimResult const &preclaimResult, ServiceRegistry &registry, OpenView &view)
Apply a prechecked transaction to an OpenView.
constexpr FlagValue spfSponsorReserve
Definition TxFlags.h:466
@ tesSUCCESS
Definition TER.h:250
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