Merge branch 'xrplf/sponsor' of https://github.com/XRPLF/rippled into mvadari/sponsor/apply-view-context

This commit is contained in:
Mayukha Vadari
2026-07-01 18:58:08 -04:00
27 changed files with 108 additions and 89 deletions

View File

@@ -39,6 +39,12 @@ isGlobalFrozen(ReadView const& view, AccountID const& issuer);
[[nodiscard]] XRPAmount
xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, beast::Journal j);
struct Adjustment
{
std::int32_t ownerCountDelta = 0;
std::int32_t accountCountDelta = 0;
};
/** Returns the account reserve, in drops.
*
* Actual owner count can be adjusted by delta in ownerCountAdj
@@ -55,12 +61,7 @@ xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj,
* @return The account reserve amount in drops
*/
[[nodiscard]] XRPAmount
accountReserve(
ReadView const& view,
SLE::const_ref sle,
beast::Journal j,
std::int32_t ownerCountAdj = 0,
std::int32_t accountCountAdj = 0);
accountReserve(ReadView const& view, SLE::const_ref sle, beast::Journal j, Adjustment adj = {});
/** Convenience overload that accepts AccountID instead of SLE.
*
@@ -72,14 +73,9 @@ accountReserve(
* @return The account reserve amount in drops
*/
[[nodiscard]] inline XRPAmount
accountReserve(
ReadView const& view,
AccountID const& id,
beast::Journal j,
std::int32_t ownerCountAdj = 0,
std::int32_t accountCountAdj = 0)
accountReserve(ReadView const& view, AccountID const& id, beast::Journal j, Adjustment adj = {})
{
return accountReserve(view, view.read(keylet::account(id)), j, ownerCountAdj, accountCountAdj);
return accountReserve(view, view.read(keylet::account(id)), j, adj);
}
/** Check if an account has insufficient reserve.
@@ -100,8 +96,7 @@ checkInsufficientReserve(
SLE::const_ref accSle,
STAmount const& accBalance,
SLE::const_ref sponsorSle,
std::int32_t ownerCountAdj,
std::int32_t accountCountAdj = 0,
Adjustment adj,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});
/** Return number of the objects which reserve is covered by the account(sle) (so called "owner

View File

@@ -61,8 +61,8 @@ escrowUnlockApplyHelper<Issue>(
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret =
checkInsufficientReserve(ctx, sleDest, xrpBalance, *sponsorSle, 1, 0, journal);
if (auto const ret = checkInsufficientReserve(
ctx, sleDest, xrpBalance, *sponsorSle, {.ownerCountDelta = 1}, journal);
!isTesSuccess(ret))
{
JLOG(journal.trace()) << "Trust line does not exist. "
@@ -190,8 +190,8 @@ escrowUnlockApplyHelper<MPTIssue>(
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret =
checkInsufficientReserve(ctx, sleDest, xrpBalance, *sponsorSle, 1, 0, journal);
if (auto const ret = checkInsufficientReserve(
ctx, sleDest, xrpBalance, *sponsorSle, {.ownerCountDelta = 1}, journal);
!isTesSuccess(ret))
return ret;

View File

@@ -315,20 +315,15 @@ decreaseOwnerCountForObject(
}
XRPAmount
accountReserve(
ReadView const& view,
SLE::const_ref sle,
beast::Journal j,
std::int32_t ownerCountAdj,
std::int32_t accountCountAdj)
accountReserve(ReadView const& view, SLE::const_ref sle, beast::Journal j, Adjustment adj)
{
if (!sle)
Throw<std::runtime_error>("xrpl::accountReserve : valid sle");
if (sle->getType() != ltACCOUNT_ROOT)
Throw<std::logic_error>("xrpl::accountReserve : valid sle type");
std::uint32_t const currentOwnerCount = ownerCount(sle, j, ownerCountAdj);
std::uint32_t const currentAccountCount = accountCountImpl(sle, accountCountAdj, j);
std::uint32_t const currentOwnerCount = ownerCount(sle, j, adj.ownerCountDelta);
std::uint32_t const currentAccountCount = accountCountImpl(sle, adj.accountCountDelta, j);
return view.fees().accountReserve(currentOwnerCount, currentAccountCount);
}
@@ -339,8 +334,7 @@ checkInsufficientReserve(
SLE::const_ref accSle,
STAmount const& accBalance,
SLE::const_ref sponsorSle,
std::int32_t ownerCountAdj,
std::int32_t accountCountAdj,
Adjustment adj,
beast::Journal j)
{
if (sponsorSle)
@@ -358,21 +352,20 @@ checkInsufficientReserve(
if (sle)
{
auto const ownerCountAllowed = sle->getFieldU32(sfRemainingOwnerCount);
if (ownerCountAllowed < ownerCountAdj)
if (adj.ownerCountDelta > 0 &&
ownerCountAllowed < static_cast<std::uint32_t>(adj.ownerCountDelta))
return tecINSUFFICIENT_RESERVE;
}
auto const sponsorBalance = sponsorSle->getFieldAmount(sfBalance);
STAmount const sponsorReserve =
accountReserve(ctx.view, sponsorSle, j, ownerCountAdj, accountCountAdj);
STAmount const sponsorReserve = accountReserve(ctx.view, sponsorSle, j, adj);
if (sponsorBalance < sponsorReserve)
return tecINSUFFICIENT_RESERVE;
}
else
{
STAmount const reserve =
accountReserve(ctx.view, accSle, j, ownerCountAdj, accountCountAdj);
STAmount const reserve = accountReserve(ctx.view, accSle, j, adj);
if (accBalance < reserve)
return tecINSUFFICIENT_RESERVE;
}

View File

@@ -210,8 +210,8 @@ authorizeMPToken(
// budget), so this check always runs for sponsored transactions.
if (sponsorSle || ownerCount(sleAcct, journal) >= 2)
{
if (auto const ret =
checkInsufficientReserve(ctx, sleAcct, priorBalance, sponsorSle, 1, 0, journal);
if (auto const ret = checkInsufficientReserve(
ctx, sleAcct, priorBalance, sponsorSle, {.ownerCountDelta = 1}, journal);
!isTesSuccess(ret))
return ret;
}

View File

@@ -913,7 +913,8 @@ tokenOfferCreateApply(
std::uint32_t txFlags)
{
Keylet const acctKeylet = keylet::account(acctID);
if (auto const acct = view.read(acctKeylet); priorBalance < accountReserve(view, acct, j, 1))
if (auto const acct = view.read(acctKeylet);
priorBalance < accountReserve(view, acct, j, {.ownerCountDelta = 1}))
return tecINSUFFICIENT_RESERVE;
auto const offerID = keylet::nftokenOffer(acctID, seqProxy.value());

View File

@@ -674,8 +674,8 @@ addEmptyHolding(
}
// Can the account cover the trust line reserve ?
if (auto const ret =
checkInsufficientReserve(ctx, sleDst, priorBalance, sponsorSle, 1, 0, journal);
if (auto const ret = checkInsufficientReserve(
ctx, sleDst, priorBalance, sponsorSle, {.ownerCountDelta = 1}, journal);
!isTesSuccess(ret))
return tecNO_LINE_INSUF_RESERVE;

View File

@@ -230,8 +230,7 @@ SponsorshipSet::doApply()
sponsorAccSle,
STAmount{sponsorBalanceAfterFee}.xrp(),
*reserveSponsorAccSle,
1,
0,
{.ownerCountDelta = 1},
ctx_.journal);
!isTesSuccess(ret))
return tecUNFUNDED;
@@ -297,8 +296,7 @@ SponsorshipSet::doApply()
sponsorAccSle,
STAmount{sponsorBalanceAfterFee}.xrp(),
*reserveSponsorAccSle,
0,
0,
{},
ctx_.journal);
!isTesSuccess(ret))
return tecUNFUNDED;

View File

@@ -326,8 +326,7 @@ SponsorshipTransfer::doApply()
sponseeSle,
sponseeSle->getFieldAmount(sfBalance),
newSponsorSle,
ownerCountDelta,
0,
{.ownerCountDelta = ownerCountDelta},
ctx_.journal);
!isTesSuccess(ter))
return ter;
@@ -380,8 +379,7 @@ SponsorshipTransfer::doApply()
sponseeSle,
sponseeSle->getFieldAmount(sfBalance),
newSponsorSle,
ownerCountDelta,
0,
{.ownerCountDelta = ownerCountDelta},
ctx_.journal);
!isTesSuccess(ter))
return ter;
@@ -429,8 +427,7 @@ SponsorshipTransfer::doApply()
ownerSle,
balanceBeforeFee(ownerSle),
SLE::pointer(),
ownerCountDelta,
0,
{.ownerCountDelta = ownerCountDelta},
ctx_.journal);
!isTesSuccess(ter))
return ter;
@@ -470,8 +467,7 @@ SponsorshipTransfer::doApply()
sponseeSle,
sponseeSle->getFieldAmount(sfBalance),
newSponsorSle,
0,
1,
{.accountCountDelta = 1},
ctx_.journal);
!isTesSuccess(ter))
return ter;
@@ -499,8 +495,7 @@ SponsorshipTransfer::doApply()
sponseeSle,
sponseeSle->getFieldAmount(sfBalance),
newSponsorSle,
0,
1,
{.accountCountDelta = 1},
ctx_.journal);
!isTesSuccess(ter))
return ter;
@@ -536,8 +531,7 @@ SponsorshipTransfer::doApply()
sponseeSle,
balanceBeforeFee(sponseeSle),
SLE::pointer(),
0,
1,
{.accountCountDelta = 1},
ctx_.journal);
!isTesSuccess(ter))
return ter;

View File

@@ -327,8 +327,7 @@ SignerListSet::replaceSignerList()
sle,
preFeeBalance_,
*sponsorSle,
kAddedOwnerCount,
0,
{.ownerCountDelta = kAddedOwnerCount},
ctx_.journal);
!isTesSuccess(ret))
return ret;

View File

@@ -1027,7 +1027,7 @@ applyCreateAccountAttestations(
// Check reserve
auto const balance = (*sleDoor)[sfBalance];
auto const reserve = accountReserve(view, sleDoor, j, 1);
auto const reserve = accountReserve(view, sleDoor, j, {.ownerCountDelta = 1});
if (balance < reserve)
return std::unexpected(tecINSUFFICIENT_RESERVE);
@@ -1434,7 +1434,7 @@ XChainCreateBridge::preclaim(PreclaimContext const& ctx)
return terNO_ACCOUNT;
auto const balance = (*sleAcc)[sfBalance];
auto const reserve = accountReserve(ctx.view, sleAcc, ctx.j, 1);
auto const reserve = accountReserve(ctx.view, sleAcc, ctx.j, {.ownerCountDelta = 1});
if (balance < reserve)
return tecINSUFFICIENT_RESERVE;
@@ -1982,7 +1982,7 @@ XChainCreateClaimID::preclaim(PreclaimContext const& ctx)
return terNO_ACCOUNT;
auto const balance = (*sleAcc)[sfBalance];
auto const reserve = accountReserve(ctx.view, sleAcc, ctx.j, 1);
auto const reserve = accountReserve(ctx.view, sleAcc, ctx.j, {.ownerCountDelta = 1});
if (balance < reserve)
return tecINSUFFICIENT_RESERVE;
}

View File

@@ -401,7 +401,12 @@ CheckCash::doApply()
// Can the account cover the trust line's or MPT reserve?
if (auto const ret = checkInsufficientReserve(
applyViewContext, sleDst, preFeeBalance_, *sponsorSle, 1, 0, j_);
applyViewContext,
sleDst,
preFeeBalance_,
*sponsorSle,
{.ownerCountDelta = 1},
j_);
!isTesSuccess(ret))
{
JLOG(j_.trace()) << "Trust line does not exist. "

View File

@@ -199,7 +199,12 @@ CheckCreate::doApply()
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkInsufficientReserve(
ctx_.getApplyViewContext(), sle, preFeeBalance_, *sponsorSle, 1, 0, ctx_.journal);
ctx_.getApplyViewContext(),
sle,
preFeeBalance_,
*sponsorSle,
{.ownerCountDelta = 1},
ctx_.journal);
!isTesSuccess(ret))
return ret;
// Note that we use the value from the sequence or ticket as the

View File

@@ -94,7 +94,7 @@ CredentialAccept::doApply()
return tefINTERNAL; // LCOV_EXCL_LINE
{
STAmount const reserve{accountReserve(view(), sleSubject, j_, 1)};
STAmount const reserve{accountReserve(view(), sleSubject, j_, {.ownerCountDelta = 1})};
if (preFeeBalance_ < reserve)
return tecINSUFFICIENT_RESERVE;
}

View File

@@ -131,7 +131,7 @@ CredentialCreate::doApply()
return tefINTERNAL; // LCOV_EXCL_LINE
{
STAmount const reserve{accountReserve(view(), sleIssuer, j_, 1)};
STAmount const reserve{accountReserve(view(), sleIssuer, j_, {.ownerCountDelta = 1})};
if (preFeeBalance_ < reserve)
return tecINSUFFICIENT_RESERVE;
}

View File

@@ -103,7 +103,12 @@ DelegateSet::doApply()
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkInsufficientReserve(
ctx_.getApplyViewContext(), sleOwner, preFeeBalance_, *sponsorSle, 1, 0, ctx_.journal);
ctx_.getApplyViewContext(),
sleOwner,
preFeeBalance_,
*sponsorSle,
{.ownerCountDelta = 1},
ctx_.journal);
!isTesSuccess(ret))
return ret;

View File

@@ -664,8 +664,9 @@ AMMWithdraw::withdraw(
std::uint32_t const currentOwnerCount = ownerCount(sleAccount, journal);
// See also TrustSet::doApply() and MPTokenAuthorize::authorize()
XRPAmount const reserve(
(currentOwnerCount < 2) ? XRPAmount(beast::kZero)
: accountReserve(view, sleAccount, journal, 1));
(currentOwnerCount < 2)
? XRPAmount(beast::kZero)
: accountReserve(view, sleAccount, journal, {.ownerCountDelta = 1}));
auto const balanceAdj = isIssue ? std::max(priorBalance, balance) : priorBalance;
if (balanceAdj < reserve)

View File

@@ -832,7 +832,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
return {tefINTERNAL, false};
{
XRPAmount const reserve = accountReserve(sb, sleCreator, viewJ, 1);
XRPAmount const reserve = accountReserve(sb, sleCreator, viewJ, {.ownerCountDelta = 1});
if (preFeeBalance_ < reserve)
{
// If we are here, the signing account had an insufficient reserve

View File

@@ -72,7 +72,8 @@ addSLE(ApplyContext& ctx, SLE::ref sle, AccountID const& owner)
// Check reserve availability for new object creation
{
auto const balance = STAmount((*sleAccount)[sfBalance]).xrp();
auto const reserve = accountReserve(ctx.view(), sleAccount, ctx.journal, 1);
auto const reserve =
accountReserve(ctx.view(), sleAccount, ctx.journal, {.ownerCountDelta = 1});
if (balance < reserve)
return tecINSUFFICIENT_RESERVE;

View File

@@ -446,7 +446,7 @@ EscrowCreate::doApply()
// unsponsored this hits the source branch and validates the
// source's pre-lock balance against base + (currentOC+1)*increment.
if (auto const ret = checkInsufficientReserve(
ctx_.getApplyViewContext(), sle, balance, *sponsorSle, 1, 0, j_);
ctx_.getApplyViewContext(), sle, balance, *sponsorSle, {.ownerCountDelta = 1}, j_);
!isTesSuccess(ret))
return ret;
@@ -467,8 +467,7 @@ EscrowCreate::doApply()
sle,
balance - STAmount(amount).xrp(),
{},
ownerCountAdj,
0,
{.ownerCountDelta = ownerCountAdj},
j_);
!isTesSuccess(ret))
return tecUNFUNDED;

View File

@@ -170,7 +170,8 @@ OracleSet::preclaim(PreclaimContext const& ctx)
if (pairs.size() > kMaxOracleDataSeries)
return tecARRAY_TOO_LARGE;
auto const reserve = accountReserve(ctx.view, sleSetter, ctx.j, adjustReserve);
auto const reserve =
accountReserve(ctx.view, sleSetter, ctx.j, {.ownerCountDelta = adjustReserve});
auto const& balance = sleSetter->getFieldAmount(sfBalance);
if (balance < reserve)

View File

@@ -166,7 +166,12 @@ DepositPreauth::doApply()
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkInsufficientReserve(
applyViewContext, sleOwner, preFeeBalance_, *sponsorSle, 1, 0, j_);
applyViewContext,
sleOwner,
preFeeBalance_,
*sponsorSle,
{.ownerCountDelta = 1},
j_);
!isTesSuccess(ret))
return ret;
@@ -214,7 +219,12 @@ DepositPreauth::doApply()
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkInsufficientReserve(
applyViewContext, sleOwner, preFeeBalance_, *sponsorSle, 1, 0, j_);
applyViewContext,
sleOwner,
preFeeBalance_,
*sponsorSle,
{.ownerCountDelta = 1},
j_);
!isTesSuccess(ret))
return ret;

View File

@@ -147,7 +147,12 @@ PaymentChannelCreate::doApply()
// unsponsored this hits the source branch and validates the
// source's pre-lock balance against base + (currentOC+1)*increment.
if (auto const ret = checkInsufficientReserve(
ctx_.getApplyViewContext(), sle, preFeeBalance_, *sponsorSle, 1, 0, j_);
ctx_.getApplyViewContext(),
sle,
preFeeBalance_,
*sponsorSle,
{.ownerCountDelta = 1},
j_);
!isTesSuccess(ret))
return ret;
@@ -165,8 +170,7 @@ PaymentChannelCreate::doApply()
sle,
preFeeBalance_ - ctx_.tx[sfAmount].xrp(),
{},
ownerCountAdj,
0,
{.ownerCountDelta = ownerCountAdj},
j_);
!isTesSuccess(ret))
return tecUNFUNDED;

View File

@@ -94,12 +94,12 @@ PaymentChannelFund::doApply()
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkInsufficientReserve(
ctx_.getApplyViewContext(), sle, balance, *sponsorSle, 0, 0, j_);
ctx_.getApplyViewContext(), sle, balance, *sponsorSle, {}, j_);
!isTesSuccess(ret))
return ret;
if (auto const ret = checkInsufficientReserve(
ctx_.getApplyViewContext(), sle, balance - ctx_.tx[sfAmount], {}, 0, 0, j_);
ctx_.getApplyViewContext(), sle, balance - ctx_.tx[sfAmount], {}, {}, j_);
!isTesSuccess(ret))
return tecUNFUNDED;
}

View File

@@ -106,7 +106,8 @@ PermissionedDomainSet::doApply()
// Create new permissioned domain.
// Check reserve availability for new object creation
auto const balance = STAmount((*ownerSle)[sfBalance]).xrp();
auto const reserve = accountReserve(ctx_.view(), ownerSle, ctx_.journal, 1);
auto const reserve =
accountReserve(ctx_.view(), ownerSle, ctx_.journal, {.ownerCountDelta = 1});
if (balance < reserve)
return tecINSUFFICIENT_RESERVE;

View File

@@ -75,7 +75,11 @@ TicketCreate::doApply()
// check the starting balance because we want to allow dipping into the
// reserve to pay fees.
std::uint32_t const ticketCount = ctx_.tx[sfTicketCount];
if (preFeeBalance_ < accountReserve(view(), sleAccountRoot, j_, ticketCount))
if (preFeeBalance_ < accountReserve(
view(),
sleAccountRoot,
j_,
{.ownerCountDelta = static_cast<std::int32_t>(ticketCount)}))
return tecINSUFFICIENT_RESERVE;
beast::Journal const viewJ{ctx_.registry.get().getJournal("View")};

View File

@@ -136,7 +136,7 @@ MPTokenIssuanceCreate::create(
if (args.priorBalance)
{
if (auto const ret = checkInsufficientReserve(
ctx, acct, *(args.priorBalance), sponsorSle, 1, 0, journal);
ctx, acct, *(args.priorBalance), sponsorSle, {.ownerCountDelta = 1}, journal);
!isTesSuccess(ret))
return std::unexpected(ret); // tecINSUFFICIENT_RESERVE
}

View File

@@ -543,8 +543,7 @@ TrustSet::doApply()
sleLowAccount,
preFeeBalance_,
*sponsorSle,
1,
0,
{.ownerCountDelta = 1},
j_);
*sponsorSle && !isTesSuccess(ret))
return tecINSUF_RESERVE_LINE;
@@ -578,8 +577,7 @@ TrustSet::doApply()
sleHighAccount,
preFeeBalance_,
*sponsorSle,
1,
0,
{.ownerCountDelta = 1},
j_);
*sponsorSle && !isTesSuccess(ret))
return tecINSUF_RESERVE_LINE;
@@ -615,7 +613,7 @@ TrustSet::doApply()
// Reserve is not scaled by load.
else if (
auto const ret = checkInsufficientReserve(
ctx_.getApplyViewContext(), sle, preFeeBalance_, *sponsorSle, 0, 0, j_);
ctx_.getApplyViewContext(), sle, preFeeBalance_, *sponsorSle, {}, j_);
!freeTrustLine && bReserveIncrease && !isTesSuccess(ret))
{
JLOG(j_.trace()) << "Delay transaction: Insufficent reserve to "
@@ -646,7 +644,12 @@ TrustSet::doApply()
}
else if (
auto const ret = checkInsufficientReserve(
ctx_.getApplyViewContext(), sle, preFeeBalance_, *sponsorSle, 1, 0, j_);
ctx_.getApplyViewContext(),
sle,
preFeeBalance_,
*sponsorSle,
{.ownerCountDelta = 1},
j_);
!freeTrustLine && !isTesSuccess(ret)) // Reserve is not scaled by load.
{
JLOG(j_.trace()) << "Delay transaction: Line does not exist. "