mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-25 16:10:57 +00:00
Merge branch 'xrplf/sponsor' into mvadari/sponsor/adjustment
This commit is contained in:
@@ -640,7 +640,28 @@ Transactor::payFee()
|
||||
if (!sle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const feeAmountAfter = sle->getFieldAmount(feePayer.balanceField) - feePaid;
|
||||
if (feePaid == beast::kZero)
|
||||
return tesSUCCESS;
|
||||
|
||||
XRPAmount balance = beast::kZero;
|
||||
if (sle->isFieldPresent(feePayer.balanceField))
|
||||
{
|
||||
balance = sle->getFieldAmount(feePayer.balanceField).xrp();
|
||||
}
|
||||
else if (feePayer.balanceField != sfFeeAmount)
|
||||
{
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
if (feePaid > balance)
|
||||
{
|
||||
if ((balance > beast::kZero) && !view().open())
|
||||
return tecINSUFF_FEE;
|
||||
|
||||
return terINSUF_FEE_B;
|
||||
}
|
||||
|
||||
auto const feeAmountAfter = balance - feePaid;
|
||||
|
||||
if (feeAmountAfter == beast::kZero && feePayer.balanceField == sfFeeAmount)
|
||||
{
|
||||
@@ -1302,7 +1323,15 @@ Transactor::reset(XRPAmount fee)
|
||||
if (!payerSle)
|
||||
return {tefINTERNAL, beast::kZero}; // LCOV_EXCL_LINE
|
||||
|
||||
auto const balance = payerSle->getFieldAmount(feePayer.balanceField).xrp();
|
||||
XRPAmount balance = beast::kZero;
|
||||
if (payerSle->isFieldPresent(feePayer.balanceField))
|
||||
{
|
||||
balance = payerSle->getFieldAmount(feePayer.balanceField).xrp();
|
||||
}
|
||||
else if (feePayer.balanceField != sfFeeAmount)
|
||||
{
|
||||
return {tefINTERNAL, beast::kZero}; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
if (feePayer.type == FeePayerType::SponsorPreFunded && payerSle->isFieldPresent(sfMaxFee))
|
||||
{
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <test/jtx/escrow.h>
|
||||
#include <test/jtx/fee.h>
|
||||
#include <test/jtx/flags.h>
|
||||
#include <test/jtx/ledgerStateFix.h>
|
||||
#include <test/jtx/mpt.h>
|
||||
#include <test/jtx/multisign.h>
|
||||
#include <test/jtx/noop.h>
|
||||
@@ -29,6 +30,7 @@
|
||||
#include <test/jtx/vault.h>
|
||||
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/basics/strHex.h>
|
||||
@@ -39,6 +41,7 @@
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/OpenView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -51,12 +54,16 @@
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/tx/apply.h>
|
||||
#include <xrpl/tx/applySteps.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
@@ -1836,6 +1843,66 @@ public:
|
||||
BEAST_EXPECT(sle->getFieldAmount(sfFeeAmount) == drops(990)); // 1000 - MaxFee(10)
|
||||
}
|
||||
|
||||
// LedgerStateFix charges an owner-reserve fee and can claim that fee
|
||||
// while returning tecFAILED_PROCESSING. That path must be safe when the
|
||||
// fee is pre-funded by a sponsorship object.
|
||||
{
|
||||
Env env{*this, testableAmendments()};
|
||||
Account const alice("alice");
|
||||
Account const sponsor("sponsor");
|
||||
env.fund(XRP(1000), alice, sponsor);
|
||||
env.close();
|
||||
|
||||
auto const fixFee = drops(env.current()->fees().increment);
|
||||
env(sponsor::set_fee(sponsor, 0, fixFee), sponsor::SponseeAcc(alice));
|
||||
env.close();
|
||||
|
||||
env(ledgerStateFix::nftPageLinks(alice, alice),
|
||||
Fee(fixFee),
|
||||
sponsor::As(sponsor, spfSponsorFee),
|
||||
Ter(tecFAILED_PROCESSING));
|
||||
|
||||
if (auto const sle = env.le(keylet::sponsorship(sponsor, alice)); BEAST_EXPECT(sle))
|
||||
BEAST_EXPECT(!sle->isFieldPresent(sfFeeAmount));
|
||||
}
|
||||
|
||||
// If preclaim saw spendable sponsored FeeAmount but the apply view no
|
||||
// longer has it, the fee path should fail cleanly instead of throwing.
|
||||
{
|
||||
Env env{*this, testableAmendments()};
|
||||
Account const alice("alice");
|
||||
Account const sponsor("sponsor");
|
||||
env.fund(XRP(1000), alice, sponsor);
|
||||
env.close();
|
||||
|
||||
auto const fixFee = drops(env.current()->fees().increment);
|
||||
env(sponsor::set_fee(sponsor, 0, fixFee), sponsor::SponseeAcc(alice));
|
||||
env.close();
|
||||
|
||||
OpenView overlay(&*env.closed());
|
||||
auto jt = env.jt(
|
||||
ledgerStateFix::nftPageLinks(alice, alice),
|
||||
Fee(fixFee),
|
||||
sponsor::As(sponsor, spfSponsorFee));
|
||||
|
||||
auto const pf = preflight(env.app(), overlay.rules(), *jt.stx, TapNone, env.journal);
|
||||
BEAST_EXPECT(isTesSuccess(pf.ter));
|
||||
auto const pc = preclaim(pf, env.app(), overlay);
|
||||
BEAST_EXPECT(isTesSuccess(pc.ter));
|
||||
|
||||
auto const original = overlay.read(keylet::sponsorship(sponsor, alice));
|
||||
if (BEAST_EXPECT(original))
|
||||
{
|
||||
auto sle = std::make_shared<SLE>(*original);
|
||||
sle->makeFieldAbsent(sfFeeAmount);
|
||||
overlay.rawReplace(sle);
|
||||
}
|
||||
|
||||
auto const result = doApply(pc, env.app(), overlay);
|
||||
BEAST_EXPECT(result.ter == terINSUF_FEE_B);
|
||||
BEAST_EXPECT(!result.applied);
|
||||
}
|
||||
|
||||
// test lsfSponsorshipRequireSignForFee
|
||||
{
|
||||
Env env{*this, testableAmendments()};
|
||||
@@ -2170,10 +2237,11 @@ public:
|
||||
std::optional<std::function<void()>> expected = std::nullopt)
|
||||
{
|
||||
using namespace test::jtx;
|
||||
// auto const sponsorOwnerCountBefore = ownerCount(env, sponsor);
|
||||
auto const sponseeOwnerCountBefore = ownerCount(env, sponsee);
|
||||
auto const sponseeSponsoredOwnerCountBefore = sponsoredOwnerCount(env, sponsee);
|
||||
auto const sponseeSponsoringOwnerCountBefore = sponsoringOwnerCount(env, sponsee);
|
||||
auto const sponsorOwnerCountBefore = ownerCount(env, sponsor);
|
||||
auto const sponsorSponsoredOwnerCountBefore = sponsoredOwnerCount(env, sponsor);
|
||||
auto const sponsorSponsoringOwnerCountBefore = sponsoringOwnerCount(env, sponsor);
|
||||
|
||||
std::optional<Sig> sponsorSig =
|
||||
@@ -2213,7 +2281,7 @@ public:
|
||||
env.close();
|
||||
}
|
||||
|
||||
if (sponsorReserveCount - 1 > 0)
|
||||
if (sponsorReserveCount > 1)
|
||||
{
|
||||
env(sponsor::set(sponsor, 0, sponsorReserveCount - 1, XRP(1)),
|
||||
sponsor::SponseeAcc(sponsee));
|
||||
@@ -2226,8 +2294,53 @@ public:
|
||||
}
|
||||
env.close();
|
||||
}
|
||||
|
||||
// A failed sponsored create must not consume prefunded reserve or mutate owner counts.
|
||||
auto const sponseeOwnerCountBeforeAttempt = ownerCount(env, sponsee);
|
||||
auto const sponseeSponsoredOwnerCountBeforeAttempt = sponsoredOwnerCount(env, sponsee);
|
||||
auto const sponseeSponsoringOwnerCountBeforeAttempt =
|
||||
sponsoringOwnerCount(env, sponsee);
|
||||
auto const sponsorOwnerCountBeforeAttempt = ownerCount(env, sponsor);
|
||||
auto const sponsorSponsoredOwnerCountBeforeAttempt = sponsoredOwnerCount(env, sponsor);
|
||||
auto const sponsorSponsoringOwnerCountBeforeAttempt =
|
||||
sponsoringOwnerCount(env, sponsor);
|
||||
auto const sponsorshipSleBeforeAttempt = env.le(keylet::sponsorship(sponsor, sponsee));
|
||||
bool const reserveCountPresentBeforeAttempt = sponsorshipSleBeforeAttempt &&
|
||||
sponsorshipSleBeforeAttempt->isFieldPresent(sfRemainingOwnerCount);
|
||||
std::uint32_t const reserveCountBeforeAttempt = reserveCountPresentBeforeAttempt
|
||||
? sponsorshipSleBeforeAttempt->getFieldU32(sfRemainingOwnerCount)
|
||||
: 0;
|
||||
|
||||
callback(env, submit(insufficientReserveResult));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(ownerCount(env, sponsee) == sponseeOwnerCountBeforeAttempt);
|
||||
BEAST_EXPECT(
|
||||
sponsoredOwnerCount(env, sponsee) == sponseeSponsoredOwnerCountBeforeAttempt);
|
||||
BEAST_EXPECT(
|
||||
sponsoringOwnerCount(env, sponsee) == sponseeSponsoringOwnerCountBeforeAttempt);
|
||||
BEAST_EXPECT(ownerCount(env, sponsor) == sponsorOwnerCountBeforeAttempt);
|
||||
BEAST_EXPECT(
|
||||
sponsoredOwnerCount(env, sponsor) == sponsorSponsoredOwnerCountBeforeAttempt);
|
||||
BEAST_EXPECT(
|
||||
sponsoringOwnerCount(env, sponsor) == sponsorSponsoringOwnerCountBeforeAttempt);
|
||||
|
||||
auto const sponsorshipSleAfterAttempt = env.le(keylet::sponsorship(sponsor, sponsee));
|
||||
BEAST_EXPECT(
|
||||
static_cast<bool>(sponsorshipSleAfterAttempt) ==
|
||||
static_cast<bool>(sponsorshipSleBeforeAttempt));
|
||||
if (sponsorshipSleAfterAttempt)
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
sponsorshipSleAfterAttempt->isFieldPresent(sfRemainingOwnerCount) ==
|
||||
reserveCountPresentBeforeAttempt);
|
||||
if (reserveCountPresentBeforeAttempt)
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
sponsorshipSleAfterAttempt->getFieldU32(sfRemainingOwnerCount) ==
|
||||
reserveCountBeforeAttempt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Success
|
||||
@@ -2250,6 +2363,13 @@ public:
|
||||
|
||||
if (!cosigning)
|
||||
{
|
||||
// Prefunded success consumes the reserved owner slot before cleanup.
|
||||
auto const sponsorshipSle = env.le(keylet::sponsorship(sponsor, sponsee));
|
||||
BEAST_EXPECT(sponsorshipSle);
|
||||
BEAST_EXPECT(
|
||||
!sponsorshipSle->isFieldPresent(sfRemainingOwnerCount) ||
|
||||
sponsorshipSle->getFieldU32(sfRemainingOwnerCount) == 0);
|
||||
|
||||
// cleanup sponsorship
|
||||
env(sponsor::del(sponsor), sponsor::SponseeAcc(sponsee));
|
||||
env.close();
|
||||
@@ -2268,6 +2388,8 @@ public:
|
||||
sponsorReserveCount);
|
||||
BEAST_EXPECT(
|
||||
sponsoringOwnerCount(env, sponsee) - sponseeSponsoringOwnerCountBefore == 0);
|
||||
BEAST_EXPECT(ownerCount(env, sponsor) == sponsorOwnerCountBefore);
|
||||
BEAST_EXPECT(sponsoredOwnerCount(env, sponsor) == sponsorSponsoredOwnerCountBefore);
|
||||
BEAST_EXPECT(
|
||||
sponsoringOwnerCount(env, sponsor) - sponsorSponsoringOwnerCountBefore ==
|
||||
sponsorReserveCount);
|
||||
@@ -2510,6 +2632,7 @@ public:
|
||||
Account const alice("alice");
|
||||
Account const sponsor("sponsor");
|
||||
Account const sponsor2("sponsor2");
|
||||
auto const credType = std::string("credType");
|
||||
|
||||
{
|
||||
Env env{*this, testableAmendments()};
|
||||
@@ -2537,6 +2660,114 @@ public:
|
||||
env.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice));
|
||||
env.close();
|
||||
// No sponsor signature here: this exercises the prefunded reassign path.
|
||||
env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
|
||||
sponsor::As(sponsor2, spfSponsorReserve));
|
||||
env.close();
|
||||
|
||||
auto const sponsor2Sle = env.le(keylet::sponsorship(sponsor2, alice));
|
||||
BEAST_EXPECT(sponsor2Sle);
|
||||
if (sponsor2Sle)
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
!sponsor2Sle->isFieldPresent(sfRemainingOwnerCount) ||
|
||||
sponsor2Sle->getFieldU32(sfRemainingOwnerCount) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 1);
|
||||
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 1);
|
||||
|
||||
// DepositPreauthDelete
|
||||
env(deposit::unauth(alice, sponsor));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 0);
|
||||
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor2) == 0);
|
||||
}
|
||||
|
||||
{
|
||||
Env env{*this, testableAmendments()};
|
||||
env.fund(XRP(1000000), alice, sponsor);
|
||||
env.close();
|
||||
auto const authCreds = std::vector<deposit::AuthorizeCredentials>{
|
||||
{.issuer = sponsor, .credType = credType}};
|
||||
auto const preauthKeylet = keylet::depositPreauth(
|
||||
alice.id(),
|
||||
std::set<std::pair<AccountID, Slice>>{
|
||||
{sponsor.id(), Slice(credType.data(), credType.size())}});
|
||||
|
||||
// Cover DepositPreauth's sfAuthorizeCredentials sponsor-reserve branch.
|
||||
testEachSponsorship(
|
||||
env,
|
||||
cosigning,
|
||||
sponsor,
|
||||
alice,
|
||||
1,
|
||||
1,
|
||||
tecINSUFFICIENT_RESERVE,
|
||||
[&](Env&, auto const& submit) {
|
||||
submit(deposit::authCredentials(alice, authCreds));
|
||||
});
|
||||
|
||||
// Cover sfUnauthorizeCredentials cleanup for a sponsored preauth object.
|
||||
BEAST_EXPECT(env.le(preauthKeylet));
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 1);
|
||||
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 1);
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 1);
|
||||
|
||||
env(deposit::unauthCredentials(alice, authCreds));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(!env.le(preauthKeylet));
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 0);
|
||||
BEAST_EXPECT(sponsoredOwnerCount(env, alice) == 0);
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testDID(bool cosigning)
|
||||
{
|
||||
testcase("DID");
|
||||
using namespace test::jtx;
|
||||
Account const alice("alice");
|
||||
Account const sponsor("sponsor");
|
||||
Account const sponsor2("sponsor2");
|
||||
|
||||
{
|
||||
Env env{*this, testableAmendments()};
|
||||
env.fund(XRP(1000000), alice, sponsor, sponsor2);
|
||||
env.close();
|
||||
|
||||
// DIDSet
|
||||
testEachSponsorship(
|
||||
env,
|
||||
cosigning,
|
||||
sponsor,
|
||||
alice,
|
||||
1,
|
||||
1,
|
||||
tecINSUFFICIENT_RESERVE,
|
||||
[&](Env& env, auto const& submit) { submit(did::set(alice), did::Uri("uri")); });
|
||||
|
||||
// transfer sponsor
|
||||
auto const keylet = keylet::did(alice);
|
||||
if (cosigning)
|
||||
{
|
||||
env(sponsor::transfer(alice, tfSponsorshipReassign, keylet.key),
|
||||
sponsor::As(sponsor2, spfSponsorReserve),
|
||||
Sig(sfSponsorSignature, sponsor2));
|
||||
env.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
env(sponsor::set_reserve(sponsor2, 0, 1), sponsor::SponseeAcc(alice));
|
||||
env.close();
|
||||
@@ -2554,11 +2785,15 @@ public:
|
||||
auto const sponsorshipSle = env.le(keylet::sponsorship(sponsor2, alice));
|
||||
BEAST_EXPECT(sponsorshipSle);
|
||||
if (sponsorshipSle)
|
||||
BEAST_EXPECT(sponsorshipSle->getFieldU32(sfRemainingOwnerCount) == 0);
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
!sponsorshipSle->isFieldPresent(sfRemainingOwnerCount) ||
|
||||
sponsorshipSle->getFieldU32(sfRemainingOwnerCount) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
// DepositPreauthDelete
|
||||
env(deposit::unauth(alice, sponsor));
|
||||
// DIDDelete
|
||||
env(did::del(alice));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(ownerCount(env, alice) == 0);
|
||||
@@ -3429,6 +3664,12 @@ public:
|
||||
auto const requiredFee = drops(env.current()->fees().increment);
|
||||
env(acctdelete(alice, bob), Fee(requiredFee), Ter(tecNO_SPONSOR_PERMISSION));
|
||||
|
||||
// The failed delete must leave the account sponsored by the original sponsor.
|
||||
auto const aliceSle = env.le(keylet::account(alice));
|
||||
BEAST_EXPECT(aliceSle);
|
||||
if (aliceSle)
|
||||
BEAST_EXPECT(aliceSle->getAccountID(sfSponsor) == sponsor.id());
|
||||
|
||||
auto const sponsorSle = env.le(keylet::account(sponsor));
|
||||
BEAST_EXPECT(sponsorSle->getFieldU32(sfSponsoringAccountCount) == 1);
|
||||
|
||||
@@ -3472,13 +3713,19 @@ public:
|
||||
// Verify sfSponsoringOwnerCount is set on sponsor
|
||||
auto const sponsorSle = env.le(keylet::account(sponsor));
|
||||
BEAST_EXPECT(sponsorSle->isFieldPresent(sfSponsoringOwnerCount));
|
||||
BEAST_EXPECT(sponsorSle->getFieldU32(sfSponsoringOwnerCount) >= 1);
|
||||
auto const sponsoringOwnerCount = sponsorSle->getFieldU32(sfSponsoringOwnerCount);
|
||||
BEAST_EXPECT(sponsoringOwnerCount >= 1);
|
||||
|
||||
incLgrSeqForAccDel(env, sponsor);
|
||||
|
||||
// AccountDelete should fail
|
||||
auto const requiredFee = drops(env.current()->fees().increment);
|
||||
env(acctdelete(sponsor, bob), Fee(requiredFee), Ter(tecHAS_OBLIGATIONS));
|
||||
// The failed delete must not decrement the outstanding sponsored-object count.
|
||||
auto const sponsorSleAfter = env.le(keylet::account(sponsor));
|
||||
BEAST_EXPECT(sponsorSleAfter->isFieldPresent(sfSponsoringOwnerCount));
|
||||
BEAST_EXPECT(
|
||||
sponsorSleAfter->getFieldU32(sfSponsoringOwnerCount) == sponsoringOwnerCount);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -3495,13 +3742,19 @@ public:
|
||||
// Verify sfSponsoringAccountCount is set on sponsor
|
||||
auto const sponsorSle = env.le(keylet::account(sponsor));
|
||||
BEAST_EXPECT(sponsorSle->isFieldPresent(sfSponsoringAccountCount));
|
||||
BEAST_EXPECT(sponsorSle->getFieldU32(sfSponsoringAccountCount) == 1);
|
||||
auto const sponsoringAccountCount = sponsorSle->getFieldU32(sfSponsoringAccountCount);
|
||||
BEAST_EXPECT(sponsoringAccountCount == 1);
|
||||
|
||||
incLgrSeqForAccDel(env, sponsor);
|
||||
|
||||
// AccountDelete should fail
|
||||
auto const requiredFee = drops(env.current()->fees().increment);
|
||||
env(acctdelete(sponsor, bob), Fee(requiredFee), Ter(tecHAS_OBLIGATIONS));
|
||||
// The failed delete must not decrement the outstanding sponsored-account count.
|
||||
auto const sponsorSleAfter = env.le(keylet::account(sponsor));
|
||||
BEAST_EXPECT(sponsorSleAfter->isFieldPresent(sfSponsoringAccountCount));
|
||||
BEAST_EXPECT(
|
||||
sponsorSleAfter->getFieldU32(sfSponsoringAccountCount) == sponsoringAccountCount);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user