mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-19 05:10:55 +00:00
Sponsor edge cases (#7558)
This commit is contained in:
@@ -319,6 +319,7 @@ words:
|
||||
- unserviced
|
||||
- unshareable
|
||||
- unshares
|
||||
- unsponsored
|
||||
- unsquelch
|
||||
- unsquelched
|
||||
- unsquelching
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -144,7 +145,7 @@ addEmptyHolding(
|
||||
if (accountID == mptIssue.getIssuer())
|
||||
return tesSUCCESS;
|
||||
|
||||
return authorizeMPToken(view, tx, priorBalance, mptID, accountID, journal);
|
||||
return authorizeMPToken(view, tx, priorBalance, mptID, accountID, journal, 0, std::nullopt);
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
@@ -193,9 +194,14 @@ authorizeMPToken(
|
||||
// - add the new mptokenKey to the owner directory
|
||||
// - create the MPToken object for the holder
|
||||
|
||||
auto const sponsorSle = getTxReserveSponsor(view, tx);
|
||||
if (!sponsorSle)
|
||||
return sponsorSle.error(); // LCOV_EXCL_LINE
|
||||
SLE::pointer sponsorSle;
|
||||
if (account == tx[sfAccount])
|
||||
{
|
||||
auto sle = getTxReserveSponsor(view, tx);
|
||||
if (!sle)
|
||||
return sle.error(); // LCOV_EXCL_LINE
|
||||
sponsorSle = std::move(*sle);
|
||||
}
|
||||
|
||||
// The reserve that is required to create the MPToken. Note
|
||||
// that although the reserve increases with every item
|
||||
@@ -205,10 +211,10 @@ authorizeMPToken(
|
||||
// The "free-tier" shortcut (ownerCount < 2) does not apply once a sponsor is on
|
||||
// the tx — the sponsor must always cover the reserve (via balance or prefunded
|
||||
// budget), so this check always runs for sponsored transactions.
|
||||
if (*sponsorSle || ownerCount(sleAcct, journal) >= 2)
|
||||
if (sponsorSle || ownerCount(sleAcct, journal) >= 2)
|
||||
{
|
||||
if (auto const ret = checkInsufficientReserve(
|
||||
view, tx, sleAcct, priorBalance, *sponsorSle, 1, 0, journal);
|
||||
view, tx, sleAcct, priorBalance, sponsorSle, 1, 0, journal);
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
@@ -235,8 +241,8 @@ authorizeMPToken(
|
||||
view.insert(mptoken);
|
||||
|
||||
// Update owner count.
|
||||
adjustOwnerCount(view, sleAcct, *sponsorSle, 1, journal);
|
||||
addSponsorToLedgerEntry(mptoken, *sponsorSle);
|
||||
adjustOwnerCount(view, sleAcct, sponsorSle, 1, journal);
|
||||
addSponsorToLedgerEntry(mptoken, sponsorSle);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -665,7 +665,9 @@ addEmptyHolding(
|
||||
return tecDUPLICATE;
|
||||
|
||||
SLE::pointer sponsorSle;
|
||||
if (!isPseudoAccount(sleDst))
|
||||
|
||||
// A reserve sponsor only covers tx.Account's own objects.
|
||||
if (!isPseudoAccount(sleDst) && accountID == tx[sfAccount])
|
||||
{
|
||||
auto sle = getTxReserveSponsor(view, tx);
|
||||
if (!sle)
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/LendingHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
@@ -56,6 +57,12 @@ LoanSet::preflight(PreflightContext const& ctx)
|
||||
|
||||
auto const& tx = ctx.tx;
|
||||
|
||||
if (tx.isFieldPresent(sfSponsorFlags) && isReserveSponsored(tx))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "LoanSet: reserve sponsorship is not allowed.";
|
||||
return temINVALID_FLAG;
|
||||
}
|
||||
|
||||
// Special case for Batch inner transactions
|
||||
if (tx.isFlag(tfInnerBatchTxn) && ctx.rules.enabled(featureBatch) &&
|
||||
!tx.isFieldPresent(sfCounterparty))
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <test/jtx/permissioned_domains.h>
|
||||
#include <test/jtx/seq.h>
|
||||
#include <test/jtx/sig.h>
|
||||
#include <test/jtx/sponsor.h>
|
||||
#include <test/jtx/tags.h>
|
||||
#include <test/jtx/ter.h>
|
||||
#include <test/jtx/trust.h>
|
||||
@@ -4436,11 +4437,12 @@ protected:
|
||||
Account const lender{"lender"};
|
||||
Account const issuer{"issuer"};
|
||||
Account const borrower{"borrower"};
|
||||
Account const sponsor{"sponsor"};
|
||||
auto const iou = issuer["IOU"];
|
||||
|
||||
auto testWrapper = [&](auto&& test) {
|
||||
Env env(*this);
|
||||
env.fund(XRP(1'000), lender, issuer, borrower);
|
||||
env.fund(XRP(1'000), lender, issuer, borrower, sponsor);
|
||||
env(trust(lender, iou(10'000'000)));
|
||||
env(pay(issuer, lender, iou(5'000'000)));
|
||||
BrokerInfo const brokerInfo{createVaultAndBroker(env, issuer["IOU"], lender)};
|
||||
@@ -4455,6 +4457,15 @@ protected:
|
||||
BrokerInfo const& brokerInfo,
|
||||
jtx::Fee const& loanSetFee,
|
||||
Number const& debtMaximumRequest) {
|
||||
for (auto const sponsorFlags : {spfSponsorReserve, spfSponsorReserve | spfSponsorFee})
|
||||
{
|
||||
env(set(borrower, brokerInfo.brokerID, debtMaximumRequest),
|
||||
sponsor::As(sponsor, sponsorFlags),
|
||||
Sig(sfCounterpartySignature, lender),
|
||||
loanSetFee,
|
||||
Ter(temINVALID_FLAG));
|
||||
}
|
||||
|
||||
// first temBAD_SIGNER: TODO
|
||||
// invalid grace period
|
||||
{
|
||||
|
||||
@@ -3088,6 +3088,105 @@ public:
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
|
||||
}
|
||||
|
||||
void
|
||||
testSponsoredTrustLineNoFreeReserve()
|
||||
{
|
||||
// An account with ownerCount < 2 may create its first trust lines even
|
||||
// without meeting the reserve. In any case, the sponsor pays the full
|
||||
// reserve in all cases, even for the sponsee's very first trust line.
|
||||
testcase("Sponsored trust line gets no free-reserve exception");
|
||||
using namespace test::jtx;
|
||||
|
||||
Account const issuer("issuer");
|
||||
Account const alice("alice");
|
||||
Account const sponsor("sponsor");
|
||||
|
||||
Env env{*this, testableAmendments()};
|
||||
env.fund(XRP(10000), issuer, alice, sponsor);
|
||||
env.close();
|
||||
|
||||
auto const usd = issuer["usd"];
|
||||
auto const lineKeylet = keylet::line(alice, issuer, usd.currency);
|
||||
|
||||
// Sponsor funded for exactly its base reserve
|
||||
adjustAccountXRPBalance(env, sponsor, reserve(env, 0));
|
||||
|
||||
// alice's ownerCount is 0, so an unsponsored first trust line would be
|
||||
// free; but because it is sponsored, the reserve check is enforced
|
||||
// against the sponsor, which is one increment short.
|
||||
env(trust(alice, usd(100)),
|
||||
sponsor::As(sponsor, spfSponsorReserve),
|
||||
Sig(sfSponsorSignature, sponsor),
|
||||
Ter(tecNO_LINE_INSUF_RESERVE));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(!env.le(lineKeylet));
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
|
||||
|
||||
// Give the sponsor has exactly one owner-reserve increment; the same
|
||||
// sponsored first trust line now succeeds and the sponsor pays for it.
|
||||
adjustAccountXRPBalance(env, sponsor, reserve(env, 1));
|
||||
|
||||
env(trust(alice, usd(100)),
|
||||
sponsor::As(sponsor, spfSponsorReserve),
|
||||
Sig(sfSponsorSignature, sponsor));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.le(lineKeylet));
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
|
||||
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 1);
|
||||
}
|
||||
|
||||
void
|
||||
testCoSignReserveBoundedBySponsorshipBudget()
|
||||
{
|
||||
// sponsor co-signs, so a fee-only object (ReserveCount == 0) makes a co-signed
|
||||
// reserve sponsorship fail -- with no fallback to the sponsor's balance.
|
||||
testcase("Co-signed reserve sponsorship is bounded by Sponsorship budget");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this, testableAmendments()};
|
||||
Account const sponsor("sponsor");
|
||||
Account const sponsee("sponsee");
|
||||
env.fund(XRP(10000), sponsor, sponsee);
|
||||
env.close();
|
||||
|
||||
// Prefund a FEE-only Sponsorship for the sponsee; ReserveCount
|
||||
// defaults to 0.
|
||||
env(sponsor::set_fee(sponsor, 0, XRP(100)), sponsor::SponseeAcc(sponsee));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.le(keylet::sponsorship(sponsor, sponsee)));
|
||||
|
||||
// Sponsee creates a Check with the sponsor co-signing the reserve. The
|
||||
// fee-only Sponsorship's has ReserveCount (0), so this fails
|
||||
// with tecINSUFFICIENT_RESERVE
|
||||
env(check::create(sponsee, sponsor, XRP(1)),
|
||||
sponsor::As(sponsor, spfSponsorReserve),
|
||||
Sig(sfSponsorSignature, sponsor),
|
||||
Ter(tecINSUFFICIENT_RESERVE));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(ownerCount(env, sponsee) == 0);
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
|
||||
BEAST_EXPECT(sponsoredOwnerCount(env, sponsee) == 0);
|
||||
|
||||
// Bumping the Sponsorship's ReserveCount budget makes the same
|
||||
// co-signed reserve sponsorship succeed, the budget is what gates it.
|
||||
env(sponsor::set_reserve(sponsor, 0, 1), sponsor::SponseeAcc(sponsee));
|
||||
env.close();
|
||||
|
||||
env(check::create(sponsee, sponsor, XRP(1)),
|
||||
sponsor::As(sponsor, spfSponsorReserve),
|
||||
Sig(sfSponsorSignature, sponsor),
|
||||
Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(ownerCount(env, sponsee) == 1);
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
|
||||
BEAST_EXPECT(sponsoredOwnerCount(env, sponsee) == 1);
|
||||
}
|
||||
|
||||
void
|
||||
testTrustSet(bool cosigning)
|
||||
{
|
||||
@@ -3876,6 +3975,8 @@ protected:
|
||||
testDelegatePermission();
|
||||
testBatch();
|
||||
|
||||
testSponsoredTrustLineNoFreeReserve();
|
||||
testCoSignReserveBoundedBySponsorshipBudget();
|
||||
testReserveSponsorGate();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user