mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-27 09:00:32 +00:00
include reserve sponsorship
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -29,35 +30,27 @@ enum class FundingKind {
|
||||
|
||||
struct FundingSource
|
||||
{
|
||||
FundingKind kind{FundingKind::AccountRoot};
|
||||
Keylet keylet{ltACCOUNT_ROOT, uint256{}};
|
||||
|
||||
// The AccountID that owns this source. For AccountRoot this is the
|
||||
// account itself; for Sponsorship this is the sponsor.
|
||||
AccountID accountId{};
|
||||
|
||||
// The source is a sponsor's account_root paying directly via tx signature
|
||||
// (no Sponsorship SLE involved). Only meaningful when kind == AccountRoot.
|
||||
bool isCoSigned{false};
|
||||
|
||||
// The source is the account, but the tx was authorized by a delegate.
|
||||
// Only meaningful when kind == AccountRoot.
|
||||
bool isDelegate{false};
|
||||
FundingKind kind;
|
||||
Keylet keylet;
|
||||
AccountID accountId;
|
||||
};
|
||||
|
||||
// Both funding sources for a transaction.
|
||||
// Both funding sources for a transaction, plus tx-level flags that apply
|
||||
// equally to fee and reserve sources.
|
||||
struct TxPayers
|
||||
{
|
||||
AccountID initiator{}; // tx[sfAccount]
|
||||
AccountID initiator; // tx[sfAccount]
|
||||
FundingSource feeSource;
|
||||
FundingSource reserveSource;
|
||||
bool isCoSigned; // tx has sfSponsorSignature
|
||||
bool isDelegate; // tx has sfDelegate
|
||||
};
|
||||
|
||||
// The SField used to read or write the balance on this source's SLE.
|
||||
inline SF_AMOUNT const&
|
||||
balanceField(FundingSource const& src)
|
||||
balanceField(FundingKind kind)
|
||||
{
|
||||
return src.kind == FundingKind::Sponsorship ? sfFeeAmount : sfBalance;
|
||||
return kind == FundingKind::Sponsorship ? sfFeeAmount : sfBalance;
|
||||
}
|
||||
|
||||
// Resolve fee + reserve funding sources for `tx` against `view`.
|
||||
@@ -65,100 +58,99 @@ balanceField(FundingSource const& src)
|
||||
inline TxPayers
|
||||
resolveTxPayers(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
TxPayers payers;
|
||||
payers.initiator = tx.getAccountID(sfAccount);
|
||||
auto const initiator = tx.getAccountID(sfAccount);
|
||||
bool const hasSponsor = tx.isFieldPresent(sfSponsor);
|
||||
bool const isCoSigned = tx.isFieldPresent(sfSponsorSignature);
|
||||
bool const isDelegate = tx.isFieldPresent(sfDelegate);
|
||||
|
||||
// --- Fee source ---
|
||||
bool const feeSponsored =
|
||||
tx.isFieldPresent(sfSponsor) && (tx.getFieldU32(sfSponsorFlags) & spfSponsorFee) != 0u;
|
||||
auto const sponsorshipKeylet = hasSponsor
|
||||
? keylet::sponsorship(tx.getAccountID(sfSponsor), initiator)
|
||||
: Keylet{ltSPONSORSHIP, uint256{}};
|
||||
|
||||
if (feeSponsored)
|
||||
{
|
||||
auto const sponsorId = tx.getAccountID(sfSponsor);
|
||||
auto const sponsorshipKeylet = keylet::sponsorship(sponsorId, payers.initiator);
|
||||
bool const hasSponsorSig = tx.isFieldPresent(sfSponsorSignature);
|
||||
|
||||
// If a Sponsorship SLE exists, fee comes from it (pre-funded). A
|
||||
// sponsor signature without an existing SLE means the sponsor is
|
||||
// paying directly from their account.
|
||||
if (hasSponsorSig && !view.exists(sponsorshipKeylet))
|
||||
auto const buildFeeSource = [&]() -> FundingSource {
|
||||
bool const feeSponsored =
|
||||
hasSponsor && (tx.getFieldU32(sfSponsorFlags) & spfSponsorFee) != 0u;
|
||||
if (!feeSponsored)
|
||||
{
|
||||
payers.feeSource = FundingSource{
|
||||
// tx.getFeePayer() returns sfDelegate if present, else sfAccount.
|
||||
auto const payerId = tx.getFeePayer();
|
||||
return FundingSource{
|
||||
.kind = FundingKind::AccountRoot,
|
||||
.keylet = keylet::account(payerId),
|
||||
.accountId = payerId,
|
||||
};
|
||||
}
|
||||
|
||||
auto const sponsorId = tx.getAccountID(sfSponsor);
|
||||
// Preserves existing dispatch: if the Sponsorship SLE exists the
|
||||
// fee comes from it; otherwise a sponsor signature pays the fee
|
||||
// from the sponsor's account_root.
|
||||
if (isCoSigned && !view.exists(sponsorshipKeylet))
|
||||
return FundingSource{
|
||||
.kind = FundingKind::AccountRoot,
|
||||
.keylet = keylet::account(sponsorId),
|
||||
.accountId = sponsorId,
|
||||
.isCoSigned = true};
|
||||
}
|
||||
else
|
||||
{
|
||||
payers.feeSource = FundingSource{
|
||||
.kind = FundingKind::Sponsorship,
|
||||
.keylet = sponsorshipKeylet,
|
||||
.accountId = sponsorId};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// tx.getFeePayer() returns sfDelegate if present, else sfAccount.
|
||||
auto const payerId = tx.getFeePayer();
|
||||
payers.feeSource = FundingSource{
|
||||
.kind = FundingKind::AccountRoot,
|
||||
.keylet = keylet::account(payerId),
|
||||
.accountId = payerId,
|
||||
.isDelegate = tx.isFieldPresent(sfDelegate)};
|
||||
}
|
||||
};
|
||||
return FundingSource{
|
||||
.kind = FundingKind::Sponsorship,
|
||||
.keylet = sponsorshipKeylet,
|
||||
.accountId = sponsorId,
|
||||
};
|
||||
};
|
||||
|
||||
// --- Reserve source ---
|
||||
// Today, reserve sponsorship is always to the sponsor's account_root.
|
||||
// There is no Sponsorship-object reserve path.
|
||||
if (auto const reserveSponsor = getTxReserveSponsorAccountID(tx))
|
||||
{
|
||||
payers.reserveSource = FundingSource{
|
||||
.kind = FundingKind::AccountRoot,
|
||||
.keylet = keylet::account(*reserveSponsor),
|
||||
.accountId = *reserveSponsor,
|
||||
.isCoSigned = true};
|
||||
}
|
||||
else
|
||||
{
|
||||
payers.reserveSource = FundingSource{
|
||||
.kind = FundingKind::AccountRoot,
|
||||
.keylet = keylet::account(payers.initiator),
|
||||
.accountId = payers.initiator};
|
||||
}
|
||||
auto const buildReserveSource = [&]() -> FundingSource {
|
||||
bool const reserveSponsored =
|
||||
hasSponsor && (tx.getFieldU32(sfSponsorFlags) & spfSponsorReserve) != 0u;
|
||||
if (!reserveSponsored)
|
||||
return FundingSource{
|
||||
.kind = FundingKind::AccountRoot,
|
||||
.keylet = keylet::account(initiator),
|
||||
.accountId = initiator,
|
||||
};
|
||||
|
||||
return payers;
|
||||
auto const sponsorId = tx.getAccountID(sfSponsor);
|
||||
return FundingSource{
|
||||
.kind = FundingKind::AccountRoot,
|
||||
.keylet = keylet::account(sponsorId),
|
||||
.accountId = sponsorId,
|
||||
};
|
||||
};
|
||||
|
||||
return TxPayers{
|
||||
.initiator = initiator,
|
||||
.feeSource = buildFeeSource(),
|
||||
.reserveSource = buildReserveSource(),
|
||||
.isCoSigned = isCoSigned,
|
||||
.isDelegate = isDelegate,
|
||||
};
|
||||
}
|
||||
|
||||
// Read the spendable balance from this source's SLE. Returns kZero if the
|
||||
// SLE is null or the field is absent.
|
||||
inline XRPAmount
|
||||
sourceBalance(SLE::const_ref sle, FundingSource const& src)
|
||||
// Read the spendable balance from a source's SLE. Returns kZero if the SLE
|
||||
// is null or the balance field is absent.
|
||||
[[nodiscard]] inline XRPAmount
|
||||
sourceBalance(SLE::const_ref sle, FundingKind kind)
|
||||
{
|
||||
if (!sle)
|
||||
return beast::kZero;
|
||||
auto const& field = balanceField(src);
|
||||
auto const& field = balanceField(kind);
|
||||
if (!sle->isFieldPresent(field))
|
||||
return beast::kZero;
|
||||
return sle->getFieldAmount(field).xrp();
|
||||
}
|
||||
|
||||
// Apply a Sponsorship object's sfMaxFee cap, if present. For AccountRoot
|
||||
// sources this is a no-op and returns `fee` unchanged.
|
||||
inline XRPAmount
|
||||
capFeeBySource(SLE::const_ref sle, FundingSource const& src, XRPAmount fee)
|
||||
// sources this is a no-op and returns `amount` unchanged.
|
||||
[[nodiscard]] inline XRPAmount
|
||||
capFeeBySource(SLE::const_ref sle, FundingKind kind, XRPAmount amount)
|
||||
{
|
||||
if (src.kind != FundingKind::Sponsorship || !sle || !sle->isFieldPresent(sfMaxFee))
|
||||
return fee;
|
||||
auto const cap = sle->getFieldAmount(sfMaxFee).xrp();
|
||||
return std::min(fee, cap);
|
||||
if (kind != FundingKind::Sponsorship || !sle || !sle->isFieldPresent(sfMaxFee))
|
||||
return amount;
|
||||
return std::min(amount, sle->getFieldAmount(sfMaxFee).xrp());
|
||||
}
|
||||
|
||||
// Deduct `amount` from `src`'s balance field on the SLE held in `view`.
|
||||
// Handles the Sponsorship case where draining sfFeeAmount to zero requires
|
||||
// makeFieldAbsent (sfFeeAmount is soeOptional on ltSPONSORSHIP).
|
||||
// Returns tefINTERNAL if the SLE is missing; tesSUCCESS otherwise.
|
||||
// The caller is responsible for `view.update(sle)`.
|
||||
// Deduct `amount` from `src`'s balance field. Handles the Sponsorship case
|
||||
// where draining sfFeeAmount to zero requires makeFieldAbsent (sfFeeAmount
|
||||
// is soeOptional on ltSPONSORSHIP). Updates the SLE in `view`.
|
||||
[[nodiscard]] inline TER
|
||||
debitSource(ApplyView& view, FundingSource const& src, XRPAmount amount)
|
||||
{
|
||||
@@ -166,7 +158,7 @@ debitSource(ApplyView& view, FundingSource const& src, XRPAmount amount)
|
||||
if (!sle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const& field = balanceField(src);
|
||||
auto const& field = balanceField(src.kind);
|
||||
auto const after = sle->getFieldAmount(field) - amount;
|
||||
if (after == beast::kZero && src.kind == FundingKind::Sponsorship)
|
||||
{
|
||||
@@ -180,4 +172,73 @@ debitSource(ApplyView& view, FundingSource const& src, XRPAmount amount)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// ----- Reserve operations against a resolved TxPayers --------------------
|
||||
// These wrap the underlying AccountRootHelpers / SponsorHelpers so transactor
|
||||
// call sites don't have to re-derive sponsor identity or branch on
|
||||
// "is this sponsored" at every step.
|
||||
|
||||
// Verify the reserve payer has sufficient balance for the proposed owner-
|
||||
// count and reserve-count deltas. When a Sponsorship SLE exists, also caps
|
||||
// against its sfRemainingOwnerCount.
|
||||
[[nodiscard]] inline TER
|
||||
checkReserve(
|
||||
ReadView const& view,
|
||||
TxPayers const& payers,
|
||||
std::int32_t ownerCountDelta,
|
||||
std::int32_t reserveCountDelta,
|
||||
beast::Journal j)
|
||||
{
|
||||
bool const reserveSponsored = payers.reserveSource.accountId != payers.initiator;
|
||||
if (reserveSponsored && ownerCountDelta > 0)
|
||||
{
|
||||
auto const sponsorshipSle =
|
||||
view.read(keylet::sponsorship(payers.reserveSource.accountId, payers.initiator));
|
||||
if (sponsorshipSle)
|
||||
{
|
||||
auto const remaining = sponsorshipSle->getFieldU32(sfRemainingOwnerCount);
|
||||
if (remaining < static_cast<std::uint32_t>(ownerCountDelta))
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
}
|
||||
|
||||
auto const reserveSle = view.read(payers.reserveSource.keylet);
|
||||
if (!reserveSle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const reserve = accountReserve(view, reserveSle, j, ownerCountDelta, reserveCountDelta);
|
||||
auto const balance = reserveSle->getFieldAmount(sfBalance);
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// Apply +/- delta to every owner-count field implicated by this tx:
|
||||
// initiator.OwnerCount (always)
|
||||
// initiator.SponsoredOwnerCount (if reserve-sponsored)
|
||||
// reserveSponsor.SponsoringOwnerCount(if reserve-sponsored)
|
||||
// Sponsorship.RemainingOwnerCount (-delta, when SLE exists and delta > 0)
|
||||
inline void
|
||||
adjustOwnerCount(ApplyView& view, TxPayers const& payers, std::int32_t delta, beast::Journal j)
|
||||
{
|
||||
auto const accountSle = view.peek(keylet::account(payers.initiator));
|
||||
SLE::pointer const sponsorSle = (payers.reserveSource.accountId != payers.initiator)
|
||||
? view.peek(payers.reserveSource.keylet)
|
||||
: SLE::pointer{};
|
||||
adjustOwnerCount(view, accountSle, sponsorSle, delta, j);
|
||||
}
|
||||
|
||||
// Stamp the reserve sponsor's AccountID onto a newly created ledger entry.
|
||||
// No-op when the tx is not reserve-sponsored.
|
||||
inline void
|
||||
stampReserveSponsor(SLE::ref sle, TxPayers const& payers, SF_ACCOUNT const& field = sfSponsor)
|
||||
{
|
||||
if (payers.reserveSource.accountId == payers.initiator)
|
||||
return;
|
||||
XRPL_ASSERT(
|
||||
(sle->getType() == ltRIPPLE_STATE && (field == sfHighSponsor || field == sfLowSponsor)) ||
|
||||
(sle->getType() != ltRIPPLE_STATE && field == sfSponsor),
|
||||
"stampReserveSponsor : valid sponsor field for ledger entry type");
|
||||
sle->setAccountID(field, payers.reserveSource.accountId);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
Reference in New Issue
Block a user