mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-14 10:40:19 +00:00
more fixes (part 5) (#7779)
This commit is contained in:
@@ -413,6 +413,17 @@ public:
|
||||
emptyDirDelete(Keylet const& directory);
|
||||
};
|
||||
|
||||
/** Bundles the mutable ledger view and the transaction being applied.
|
||||
|
||||
Passed together to avoid threading two separate parameters through every
|
||||
helper that needs both the view (for state reads/writes) and the
|
||||
transaction (for field inspection and metadata).
|
||||
|
||||
Both members are non-owning references; the caller is responsible for
|
||||
ensuring that the referenced objects outlive the ApplyViewContext.
|
||||
|
||||
TODO: replace with ApplyContext after it's untangled with xrpl/tx
|
||||
*/
|
||||
struct ApplyViewContext
|
||||
{
|
||||
ApplyView& view;
|
||||
|
||||
@@ -43,7 +43,7 @@ struct OwnerCounts
|
||||
}
|
||||
|
||||
if (x > std::numeric_limits<std::uint32_t>::max())
|
||||
return std::numeric_limits<std::uint32_t>::max();
|
||||
return std::numeric_limits<std::uint32_t>::max(); // LCOV_EXCL_LINE
|
||||
return static_cast<std::uint32_t>(x);
|
||||
}
|
||||
|
||||
@@ -65,15 +65,6 @@ struct OwnerCounts
|
||||
return this == &o ||
|
||||
(owner == o.owner && sponsored == o.sponsored && sponsoring == o.sponsoring);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
valid() const
|
||||
{
|
||||
int64_t const x = static_cast<int64_t>(owner) - sponsored + sponsoring;
|
||||
if (x < 0 || x > std::numeric_limits<std::uint32_t>::max())
|
||||
return false;
|
||||
return owner >= sponsored;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -68,7 +68,7 @@ escrowUnlockApplyHelper<Issue>(
|
||||
if (!ctx.view.exists(trustLineKey) && createAsset)
|
||||
{
|
||||
// Can the account cover the trust line's reserve?
|
||||
auto const sponsorSle = getTxReserveSponsor(ctx);
|
||||
auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, sleDest);
|
||||
if (!sponsorSle)
|
||||
return sponsorSle.error(); // LCOV_EXCL_LINE
|
||||
|
||||
@@ -197,7 +197,7 @@ escrowUnlockApplyHelper<MPTIssue>(
|
||||
auto const mptKeylet = keylet::mptoken(issuanceKey.key, receiver);
|
||||
if (!ctx.view.exists(mptKeylet) && createAsset && !receiverIssuer)
|
||||
{
|
||||
auto const sponsorSle = getTxReserveSponsor(ctx);
|
||||
auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, sleDest);
|
||||
if (!sponsorSle)
|
||||
return sponsorSle.error(); // LCOV_EXCL_LINE
|
||||
|
||||
|
||||
@@ -93,12 +93,6 @@ getLedgerEntryReserveSponsor(
|
||||
SLE::const_ref sle,
|
||||
SF_ACCOUNT const& field = sfSponsor);
|
||||
|
||||
SLE::const_pointer
|
||||
getLedgerEntryReserveSponsor(
|
||||
ReadView const& view,
|
||||
SLE::const_ref sle,
|
||||
SF_ACCOUNT const& field = sfSponsor);
|
||||
|
||||
void
|
||||
addSponsorToLedgerEntry(
|
||||
SLE::ref sle,
|
||||
|
||||
@@ -1163,8 +1163,9 @@ TRANSACTION(ttCONFIDENTIAL_MPT_CLAWBACK, 89, ConfidentialMPTClawback,
|
||||
{sfHolder, SoeRequired},
|
||||
{sfMPTAmount, SoeRequired},
|
||||
{sfZKProof, SoeRequired},
|
||||
}))/** This transaction transfer sponsorship */
|
||||
}))
|
||||
|
||||
/** This transaction transfers sponsorship on an object/account. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/sponsor/SponsorshipTransfer.h>
|
||||
#endif
|
||||
@@ -1177,7 +1178,7 @@ TRANSACTION(ttSPONSORSHIP_TRANSFER, 90, SponsorshipTransfer,
|
||||
{sfSponsee, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This transaction create sponsorship object */
|
||||
/** This transaction creates a Sponsorship object. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/sponsor/SponsorshipSet.h>
|
||||
#endif
|
||||
|
||||
@@ -942,8 +942,7 @@ createMPToken(
|
||||
(*mptoken)[sfFlags] = flags;
|
||||
(*mptoken)[sfOwnerNode] = *ownerNode;
|
||||
|
||||
if (sponsorSle)
|
||||
addSponsorToLedgerEntry(mptoken, sponsorSle);
|
||||
addSponsorToLedgerEntry(mptoken, sponsorSle);
|
||||
|
||||
view.insert(mptoken);
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ getTxReserveSponsor(ApplyViewContext ctx)
|
||||
|
||||
// already checked in Transactor::checkSponsor
|
||||
if (!sle)
|
||||
return std::unexpected(tecINTERNAL);
|
||||
return std::unexpected(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
return sle;
|
||||
}
|
||||
return SLE::pointer();
|
||||
@@ -89,15 +89,6 @@ getLedgerEntryReserveSponsor(ApplyView& view, SLE::const_ref sle, SF_ACCOUNT con
|
||||
return {};
|
||||
}
|
||||
|
||||
SLE::const_pointer
|
||||
getLedgerEntryReserveSponsor(ReadView const& view, SLE::const_ref sle, SF_ACCOUNT const& field)
|
||||
{
|
||||
auto const sponsorID = getLedgerEntryReserveSponsorID(sle, field);
|
||||
if (sponsorID)
|
||||
return view.read(keylet::account(*sponsorID));
|
||||
return {};
|
||||
}
|
||||
|
||||
void
|
||||
addSponsorToLedgerEntry(SLE::ref sle, SLE::const_ref sponsorSle, SF_ACCOUNT const& field)
|
||||
{
|
||||
|
||||
@@ -409,7 +409,7 @@ Transactor::checkSponsor(ReadView const& view, STTx const& tx)
|
||||
|
||||
// Reserve sponsorship with permissioned delegation is disallowed.
|
||||
if (tx.isFieldPresent(sfDelegate) && isReserveSponsored(tx))
|
||||
return terNO_PERMISSION;
|
||||
return temINVALID;
|
||||
|
||||
if (auto const sponsorSle = getTxReserveSponsor(view, tx); !sponsorSle)
|
||||
return terNO_ACCOUNT;
|
||||
@@ -641,8 +641,9 @@ Transactor::payFee()
|
||||
}
|
||||
|
||||
// A co-signed sponsor pays the fee out of its own account balance, but must
|
||||
// never be charged into its account reserve. Mirror the spendable amount
|
||||
// computed in checkFee() so the reserve is protected on the apply path too.
|
||||
// never be charged into its account reserve, and a pre-funded sponsorship's
|
||||
// fee is capped by sfMaxFee. Mirror the spendable amount computed in
|
||||
// checkFee() so both limits are enforced on the apply path too.
|
||||
XRPAmount spendable = balance;
|
||||
if (feePayer.type == FeePayerType::SponsorCoSigned)
|
||||
{
|
||||
@@ -650,6 +651,11 @@ Transactor::payFee()
|
||||
// max(balance - reserve, 0) with overflow handling
|
||||
spendable = balance > sponsorReserve ? balance - sponsorReserve : beast::kZero;
|
||||
}
|
||||
else if (feePayer.type == FeePayerType::SponsorPreFunded && sle->isFieldPresent(sfMaxFee))
|
||||
{
|
||||
auto const cap = sle->getFieldAmount(sfMaxFee).xrp();
|
||||
spendable = std::min(spendable, cap);
|
||||
}
|
||||
|
||||
// Only sponsor fee-payers reject here on insufficient funds. For an
|
||||
// ordinary account, the fee falls through and is capped by reset(), which
|
||||
|
||||
@@ -222,7 +222,6 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -273,6 +273,12 @@ SponsorshipTransfer::preclaim(PreclaimContext const& ctx)
|
||||
// be sponsored
|
||||
if (!newSponsorSle || !isSponsored)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Reassigning to the current sponsor would change no state, but would
|
||||
// still draw down the sponsor's pre-funded reserve budget (and its
|
||||
// reserve headroom would be double-counted in checkReserve).
|
||||
if (targetSle->getAccountID(*sponsorField) == ctx.tx.getAccountID(sfSponsor))
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
else if (ctx.tx.isFlag(tfSponsorshipEnd))
|
||||
{
|
||||
|
||||
@@ -1029,6 +1029,15 @@ public:
|
||||
BEAST_EXPECT(sle2->isFieldPresent(sfSponsor));
|
||||
BEAST_EXPECT(sle2->getAccountID(sfSponsor) == sponsor2.id());
|
||||
|
||||
// Reassign to the current sponsor is a no-op and is rejected
|
||||
env(sponsor::transfer(alice, tfSponsorshipReassign),
|
||||
sponsor::As(sponsor2, spfSponsorReserve),
|
||||
Sig(sfSponsorSignature, sponsor2),
|
||||
Ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(sponsoringAccountCount(env, sponsor2) == 1);
|
||||
|
||||
// sponsor 2 accounts
|
||||
adjustAccountXRPBalance(env, sponsor2, accountReserve(env, 3));
|
||||
env(sponsor::transfer(bob, tfSponsorshipCreate),
|
||||
@@ -1178,6 +1187,15 @@ public:
|
||||
Ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
// Reassign to the current sponsor is a no-op and is rejected
|
||||
env(sponsor::transfer(alice, tfSponsorshipReassign, checkId),
|
||||
sponsor::As(sponsor1, spfSponsorReserve),
|
||||
Sig(sfSponsorSignature, sponsor1),
|
||||
Ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 1);
|
||||
|
||||
// transfer sponsor
|
||||
env(sponsor::transfer(alice, tfSponsorshipReassign, checkId),
|
||||
sponsor::As(sponsor2, spfSponsorReserve),
|
||||
@@ -1322,6 +1340,17 @@ public:
|
||||
auto sponsor1Sle = env.le(keylet::sponsorship(sponsor1, alice));
|
||||
BEAST_EXPECT(sponsor1Sle->getFieldU32(sfRemainingOwnerCount) == 99);
|
||||
|
||||
// Reassign to the current sponsor is rejected and must not draw
|
||||
// down the pre-funded reserve budget
|
||||
env(sponsor::transfer(alice, tfSponsorshipReassign, checkId),
|
||||
sponsor::As(sponsor1, spfSponsorReserve),
|
||||
Ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
sponsor1Sle = env.le(keylet::sponsorship(sponsor1, alice));
|
||||
BEAST_EXPECT(sponsor1Sle->getFieldU32(sfRemainingOwnerCount) == 99);
|
||||
BEAST_EXPECT(sponsoringOwnerCount(env, sponsor1) == 1);
|
||||
|
||||
// transfer sponsor
|
||||
env(sponsor::set_reserve(sponsor2, 0, 100), sponsor::SponseeAcc(alice));
|
||||
env.close();
|
||||
@@ -4539,7 +4568,7 @@ public:
|
||||
delegate::As(bob),
|
||||
sponsor::As(sponsor, spfSponsorReserve),
|
||||
Sig(sfSponsorSignature, sponsor),
|
||||
Ter(terNO_PERMISSION));
|
||||
Ter(temINVALID));
|
||||
}
|
||||
|
||||
// Pre-funded reserve sponsorship is blocked for delegated transactions.
|
||||
@@ -4555,7 +4584,7 @@ public:
|
||||
env(check::create(alice, carol, XRP(1)),
|
||||
delegate::As(bob),
|
||||
sponsor::As(sponsor, spfSponsorReserve),
|
||||
Ter(terNO_PERMISSION));
|
||||
Ter(temINVALID));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1467,8 +1467,8 @@ public:
|
||||
BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 0);
|
||||
}
|
||||
|
||||
// Only the queried side of a shared trust line should determine
|
||||
// sponsorship classification.
|
||||
// A trust line sponsored on either side is classified as sponsored
|
||||
// for both parties.
|
||||
{
|
||||
Env env(*this, testableAmendments());
|
||||
Account const issuer("issuer");
|
||||
@@ -1519,13 +1519,59 @@ public:
|
||||
{
|
||||
auto const resp = acctObjsSponsored(env, issuer.id(), true, jss::state);
|
||||
auto const& objs = resp[jss::result][jss::account_objects];
|
||||
BEAST_EXPECT(objs.size() == 0);
|
||||
if (BEAST_EXPECT(objs.size() == 1))
|
||||
BEAST_EXPECT(objs[0u][sfLedgerEntryType.jsonName] == jss::RippleState);
|
||||
}
|
||||
{
|
||||
auto const resp = acctObjsSponsored(env, issuer.id(), false, jss::state);
|
||||
auto const& objs = resp[jss::result][jss::account_objects];
|
||||
if (BEAST_EXPECT(objs.size() == 1))
|
||||
BEAST_EXPECT(objs[0u][sfLedgerEntryType.jsonName] == jss::RippleState);
|
||||
BEAST_EXPECT(objs.size() == 0);
|
||||
}
|
||||
}
|
||||
|
||||
// A sponsored Check is classified as sponsored in both the writer's
|
||||
// and the destination's results.
|
||||
{
|
||||
Env env(*this, testableAmendments());
|
||||
Account const owner("owner");
|
||||
Account const dest("dest");
|
||||
Account const sponsor("sponsor");
|
||||
|
||||
env.fund(XRP(10000), owner, dest, sponsor);
|
||||
env.close();
|
||||
|
||||
auto const checkSeq = env.seq(owner);
|
||||
env(check::create(owner, dest, XRP(1)));
|
||||
env.close();
|
||||
|
||||
auto const checkId = keylet::check(owner, checkSeq);
|
||||
if (!BEAST_EXPECT(env.le(checkId)))
|
||||
return;
|
||||
|
||||
env(sponsor::transfer(owner, tfSponsorshipCreate, checkId.key),
|
||||
sponsor::As(sponsor, spfSponsorReserve),
|
||||
Sig(sfSponsorSignature, sponsor));
|
||||
env.close();
|
||||
|
||||
{
|
||||
auto const sle = env.le(checkId);
|
||||
if (!BEAST_EXPECT(sle))
|
||||
return;
|
||||
BEAST_EXPECT(sle->isFieldPresent(sfSponsor));
|
||||
}
|
||||
|
||||
for (auto const& acct : {owner.id(), dest.id()})
|
||||
{
|
||||
{
|
||||
auto const resp = acctObjsSponsored(env, acct, true, jss::check);
|
||||
auto const& objs = resp[jss::result][jss::account_objects];
|
||||
if (BEAST_EXPECT(objs.size() == 1))
|
||||
BEAST_EXPECT(objs[0u][sfLedgerEntryType.jsonName] == jss::Check);
|
||||
}
|
||||
{
|
||||
auto const resp = acctObjsSponsored(env, acct, false, jss::check);
|
||||
BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -378,6 +378,23 @@ class Simulate_test : public beast::unit_test::Suite
|
||||
resp[jss::result][jss::error_message] ==
|
||||
"Invalid field 'SponsorSignature', not object.");
|
||||
}
|
||||
{
|
||||
// Invalid SponsorSignature.Signers field
|
||||
json::Value params;
|
||||
json::Value txJson = json::ValueType::Object;
|
||||
txJson[jss::TransactionType] = jss::AccountSet;
|
||||
txJson[jss::Account] = env.master.human();
|
||||
json::Value sponsorSignature = json::ValueType::Object;
|
||||
sponsorSignature[sfSigners] = "1";
|
||||
txJson[sfSponsorSignature] = sponsorSignature;
|
||||
params[jss::tx_json] = txJson;
|
||||
|
||||
auto const resp = env.rpc("json", "simulate", to_string(params));
|
||||
BEAST_EXPECTS(
|
||||
resp[jss::result][jss::error_message] ==
|
||||
"Invalid field 'tx.SponsorSignature.Signers'.",
|
||||
resp.toStyledString());
|
||||
}
|
||||
{
|
||||
// Invalid transaction
|
||||
json::Value params;
|
||||
|
||||
@@ -197,20 +197,19 @@ getAccountObjects(
|
||||
!typeMatchesFilter(typeFilter.value(), sleNode->getType()))
|
||||
canAppend = false;
|
||||
|
||||
// An object counts as sponsored no matter which party's directory
|
||||
// it was found through; the sponsorship need not belong to
|
||||
// `account`'s side.
|
||||
std::optional<AccountID> sponsor;
|
||||
if (sleNode->getType() == ltSPONSORSHIP)
|
||||
if (sleNode->getType() == ltRIPPLE_STATE)
|
||||
{
|
||||
// ltSPONSORSHIP is in both parties' directories; only the
|
||||
// owner's side carries a sponsor.
|
||||
if (sleNode->getAccountID(sfOwner) == account)
|
||||
sponsor = getLedgerEntryReserveSponsorID(sleNode);
|
||||
sponsor = getLedgerEntryReserveSponsorID(sleNode, sfHighSponsor);
|
||||
if (!sponsor)
|
||||
sponsor = getLedgerEntryReserveSponsorID(sleNode, sfLowSponsor);
|
||||
}
|
||||
else if (
|
||||
isLedgerEntrySupportedBySponsorship(*sleNode) &&
|
||||
getLedgerEntryOwner(ledger, *sleNode, account).has_value())
|
||||
else if (isLedgerEntrySupportedBySponsorship(*sleNode))
|
||||
{
|
||||
sponsor = getLedgerEntryReserveSponsorID(
|
||||
sleNode, getLedgerEntrySponsorField(*sleNode, account));
|
||||
sponsor = getLedgerEntryReserveSponsorID(sleNode);
|
||||
}
|
||||
|
||||
if (!sponsoredMatchesFilter(sponsor))
|
||||
|
||||
@@ -77,7 +77,7 @@ getAutofillSequence(json::Value const& txJson, RPC::JsonContext& context)
|
||||
}
|
||||
|
||||
static std::optional<json::Value>
|
||||
autofillSignature(json::Value& sigObject)
|
||||
autofillSignature(json::Value& sigObject, std::string const& fieldPrefix = "tx")
|
||||
{
|
||||
if (!sigObject.isMember(jss::SigningPubKey))
|
||||
{
|
||||
@@ -88,14 +88,17 @@ autofillSignature(json::Value& sigObject)
|
||||
if (sigObject.isMember(jss::Signers))
|
||||
{
|
||||
if (!sigObject[jss::Signers].isArray())
|
||||
return RPC::invalidFieldError("tx.Signers");
|
||||
return RPC::invalidFieldError(fieldPrefix + ".Signers");
|
||||
// check multisigned signers
|
||||
for (unsigned index = 0; index < sigObject[jss::Signers].size(); index++)
|
||||
{
|
||||
auto& signer = sigObject[jss::Signers][index];
|
||||
if (!signer.isObject() || !signer.isMember(jss::Signer) ||
|
||||
!signer[jss::Signer].isObject())
|
||||
return RPC::invalidFieldError("tx.Signers[" + std::to_string(index) + "]");
|
||||
{
|
||||
return RPC::invalidFieldError(
|
||||
fieldPrefix + ".Signers[" + std::to_string(index) + "]");
|
||||
}
|
||||
|
||||
if (!signer[jss::Signer].isMember(jss::SigningPubKey))
|
||||
{
|
||||
@@ -141,7 +144,7 @@ autofillTx(json::Value& txJson, RPC::JsonContext& context)
|
||||
if (!sponsorSignature.isObject())
|
||||
return RPC::objectFieldError(sfSponsorSignature.jsonName);
|
||||
|
||||
if (auto const error = autofillSignature(sponsorSignature))
|
||||
if (auto const error = autofillSignature(sponsorSignature, "tx.SponsorSignature"))
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user