|
7 | 7 | #include <test/jtx/mpt.h> |
8 | 8 | #include <test/jtx/pay.h> |
9 | 9 | #include <test/jtx/permissioned_domains.h> |
| 10 | +#include <test/jtx/sig.h> |
| 11 | +#include <test/jtx/sponsor.h> |
10 | 12 | #include <test/jtx/tags.h> |
11 | 13 | #include <test/jtx/token.h> |
12 | 14 | #include <test/jtx/trust.h> |
@@ -5769,6 +5771,139 @@ class Invariants_test : public beast::unit_test::Suite |
5769 | 5771 | return true; |
5770 | 5772 | }); |
5771 | 5773 | } |
| 5774 | + |
| 5775 | + { |
| 5776 | + // XRPNotCreated: the sfFeeAmount escrowed in a Sponsorship entry |
| 5777 | + // counts toward the XRP total. Creating a Sponsorship holding a |
| 5778 | + // FeeAmount without debiting any account manufactures XRP. |
| 5779 | + doInvariantCheck( |
| 5780 | + {{"XRP net change was positive: 100000000"}}, |
| 5781 | + [](Account const& a1, Account const& a2, ApplyContext& ac) { |
| 5782 | + auto const sleNew = |
| 5783 | + std::make_shared<SLE>(keylet::sponsorship(a1.id(), a2.id())); |
| 5784 | + sleNew->setAccountID(sfOwner, a1.id()); |
| 5785 | + sleNew->setAccountID(sfSponsee, a2.id()); |
| 5786 | + sleNew->setFieldAmount(sfFeeAmount, XRP(100)); |
| 5787 | + ac.view().insert(sleNew); |
| 5788 | + return true; |
| 5789 | + }); |
| 5790 | + |
| 5791 | + // ... and deleting a Sponsorship entry without refunding its |
| 5792 | + // escrowed FeeAmount destroys XRP beyond the transaction fee. |
| 5793 | + doInvariantCheck( |
| 5794 | + {{"XRP net change of -100000000 doesn't match fee 0"}}, |
| 5795 | + [](Account const& a1, Account const& a2, ApplyContext& ac) { |
| 5796 | + auto const sle = ac.view().peek(keylet::sponsorship(a2.id(), a1.id())); |
| 5797 | + if (!sle) |
| 5798 | + return false; |
| 5799 | + ac.view().erase(sle); |
| 5800 | + return true; |
| 5801 | + }, |
| 5802 | + XRPAmount{}, |
| 5803 | + STTx{ttACCOUNT_SET, [](STObject&) {}}, |
| 5804 | + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}, |
| 5805 | + [](Account const& a1, Account const& a2, Env& env) { |
| 5806 | + // a2 sponsors a1's fees with a pre-funded XRP(100) |
| 5807 | + env(sponsor::set_fee(a2, 0, XRP(100)), sponsor::SponseeAcc(a1)); |
| 5808 | + env.close(); |
| 5809 | + return env.le(keylet::sponsorship(a2.id(), a1.id())) != nullptr; |
| 5810 | + }); |
| 5811 | + } |
| 5812 | + |
| 5813 | + { |
| 5814 | + // Deleting a sponsored object without adjusting the accounts' |
| 5815 | + // Sponsored/SponsoringOwnerCount fields must be caught (the |
| 5816 | + // isDelete path of SponsorshipOwnerCountsMatch::visitEntry: the |
| 5817 | + // sponsored-object delta is -1 while the account deltas are 0). |
| 5818 | + auto const expectMessage = |
| 5819 | + "SponsoredObjectOwnerCount does not equal SponsoredOwnerCount delta."; |
| 5820 | + uint256 checkID; |
| 5821 | + |
| 5822 | + doInvariantCheck( |
| 5823 | + {{expectMessage}}, |
| 5824 | + [&](Account const&, Account const&, ApplyContext& ac) { |
| 5825 | + auto const check = ac.view().peek(keylet::check(checkID)); |
| 5826 | + if (!check) |
| 5827 | + return false; |
| 5828 | + ac.view().erase(check); |
| 5829 | + return true; |
| 5830 | + }, |
| 5831 | + XRPAmount{}, |
| 5832 | + STTx{ttACCOUNT_SET, [](STObject&) {}}, |
| 5833 | + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}, |
| 5834 | + [&checkID](Account const& a1, Account const& a2, Env& env) { |
| 5835 | + // a2 reserve-sponsors a1's check by co-signing its creation |
| 5836 | + checkID = keylet::check(a1.id(), env.seq(a1)).key; |
| 5837 | + env(check::create(a1, a2, XRP(1)), |
| 5838 | + sponsor::As(a2, spfSponsorReserve), |
| 5839 | + Sig(sfSponsorSignature, a2), |
| 5840 | + Fee(XRP(1))); |
| 5841 | + env.close(); |
| 5842 | + auto const sle = env.le(keylet::check(checkID)); |
| 5843 | + return sle && sle->isFieldPresent(sfSponsor); |
| 5844 | + }); |
| 5845 | + } |
| 5846 | + |
| 5847 | + { |
| 5848 | + // Trust lines use sfLowSponsor/sfHighSponsor instead of sfSponsor |
| 5849 | + // and can be reserve-sponsored independently on each side. |
| 5850 | + auto const expectMessage = |
| 5851 | + "SponsoredObjectOwnerCount does not equal SponsoredOwnerCount delta."; |
| 5852 | + |
| 5853 | + auto const preclose = [](Account const& a1, Account const& a2, Env& env) { |
| 5854 | + env(trust(a1, a2["USD"](100))); |
| 5855 | + return true; |
| 5856 | + }; |
| 5857 | + |
| 5858 | + // Marking one side of an existing trust line as sponsored without |
| 5859 | + // touching any account counts (object delta 1, account deltas 0). |
| 5860 | + doInvariantCheck( |
| 5861 | + {{expectMessage}}, |
| 5862 | + [](Account const& a1, Account const& a2, ApplyContext& ac) { |
| 5863 | + auto const line = ac.view().peek(keylet::trustLine(a1, a2, a2["USD"].currency)); |
| 5864 | + if (!line) |
| 5865 | + return false; |
| 5866 | + line->setAccountID(sfLowSponsor, a2.id()); |
| 5867 | + ac.view().update(line); |
| 5868 | + return true; |
| 5869 | + }, |
| 5870 | + XRPAmount{}, |
| 5871 | + STTx{ttACCOUNT_SET, [](STObject&) {}}, |
| 5872 | + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}, |
| 5873 | + preclose); |
| 5874 | + |
| 5875 | + // A trust line sponsored on both sides contributes two sponsored |
| 5876 | + // owner counts, so bumping the account fields by only one each is |
| 5877 | + // caught (object delta 2, account deltas 1). |
| 5878 | + doInvariantCheck( |
| 5879 | + {{expectMessage}}, |
| 5880 | + [](Account const& a1, Account const& a2, ApplyContext& ac) { |
| 5881 | + auto const line = ac.view().peek(keylet::trustLine(a1, a2, a2["USD"].currency)); |
| 5882 | + if (!line) |
| 5883 | + return false; |
| 5884 | + line->setAccountID(sfLowSponsor, a2.id()); |
| 5885 | + line->setAccountID(sfHighSponsor, a2.id()); |
| 5886 | + ac.view().update(line); |
| 5887 | + |
| 5888 | + // a1 owns the trust line, so its OwnerCount is already 1. |
| 5889 | + auto const sponsee = ac.view().peek(keylet::account(a1.id())); |
| 5890 | + if (!sponsee) |
| 5891 | + return false; |
| 5892 | + sponsee->setFieldU32(sfSponsoredOwnerCount, 1); |
| 5893 | + ac.view().update(sponsee); |
| 5894 | + |
| 5895 | + auto const sponsoring = ac.view().peek(keylet::account(a2.id())); |
| 5896 | + if (!sponsoring) |
| 5897 | + return false; |
| 5898 | + sponsoring->setFieldU32(sfSponsoringOwnerCount, 1); |
| 5899 | + ac.view().update(sponsoring); |
| 5900 | + return true; |
| 5901 | + }, |
| 5902 | + XRPAmount{}, |
| 5903 | + STTx{ttACCOUNT_SET, [](STObject&) {}}, |
| 5904 | + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}, |
| 5905 | + preclose); |
| 5906 | + } |
5772 | 5907 | } |
5773 | 5908 |
|
5774 | 5909 | void |
|
0 commit comments