Merge branch 'develop' into cosign-v1-merge-develop

# Conflicts:
#	include/xrpl/protocol/detail/features.macro
This commit is contained in:
Zhiyuan Wang
2026-09-08 15:34:09 -04:00
173 changed files with 12895 additions and 1482 deletions

View File

@@ -34,7 +34,10 @@ enum class HashRouterFlags : std::uint16_t {
PRIVATE4 = 0x0800,
// Used in EscrowFinish.cpp
PRIVATE5 = 0x1000,
PRIVATE6 = 0x2000
PRIVATE6 = 0x2000,
// Used in apply.cpp
PRIVATE7 = 0x4000,
PRIVATE8 = 0x8000
};
constexpr HashRouterFlags

View File

@@ -15,7 +15,6 @@
#include <cstdint>
#include <expected>
#include <optional>
#include <set>
#include <vector>
namespace xrpl {
@@ -353,14 +352,14 @@ pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey);
*
* The list is constructed during initialization and is const after that.
* Pseudo-account designator fields MUST be maintained by including the
* SField::sMD_PseudoAccount flag in the SField definition.
* SField::kSmdPseudoAccount flag in the SField definition.
*/
[[nodiscard]] std::vector<SField const*> const&
getPseudoAccountFields();
/**
* Returns true if and only if sleAcct is a pseudo-account or specific
* pseudo-accounts in pseudoFieldFilter.
* Returns true if and only if sleAcct is a pseudo-account of any kind
* (i.e. carries at least one field flagged with SField::kSmdPseudoAccount).
*
* Returns false if sleAcct is:
* - NOT a pseudo-account OR
@@ -368,18 +367,15 @@ getPseudoAccountFields();
* - null pointer
*/
[[nodiscard]] bool
isPseudoAccount(SLE::const_pointer sleAcct, std::set<SField const*> const& pseudoFieldFilter = {});
isPseudoAccount(SLE::const_pointer sleAcct);
/**
* Convenience overload that reads the account from the view.
*/
[[nodiscard]] inline bool
isPseudoAccount(
ReadView const& view,
AccountID const& accountId,
std::set<SField const*> const& pseudoFieldFilter = {})
isPseudoAccount(ReadView const& view, AccountID const& accountId)
{
return isPseudoAccount(view.read(keylet::account(accountId)), pseudoFieldFilter);
return isPseudoAccount(view.read(keylet::account(accountId)));
}
/**

View File

@@ -14,7 +14,6 @@
#include <xrpl/protocol/STVector256.h>
#include <xrpl/protocol/TER.h>
#include <cstdint>
#include <memory>
#include <set>
#include <utility>
@@ -34,32 +33,6 @@ checkExpired(SLE const& sleCredential, NetClock::time_point const& closed);
[[nodiscard]] TER
deleteSLE(ApplyView& view, SLE::ref sleCredential, beast::Journal j);
/**
* @brief Remove credentials pinned to a pseudo-account's owner directory.
*
* Cleans up credentials that were linked to a pseudo-account (Vault, LoanBroker,
* AMM), which such an account can neither accept nor delete. Only credentials
* are removed; every other object is left in place. The walk visits at most
* @p maxNodesToDelete directory entries and charges the ones it leaves alone
* against that budget too, so a directory holding other objects yields fewer
* than @p maxNodesToDelete deletions. On reaching the bound the result is
* `tecINCOMPLETE` and the caller must propagate it so a later transaction
* resumes.
*
* @param view Mutable ledger view.
* @param pseudoAcct The pseudo-account whose directory is cleaned.
* @param maxNodesToDelete Upper bound on directory entries processed in one call.
* @param j Journal for diagnostics.
* @return tesSUCCESS once no credentials remain, tecINCOMPLETE if the bound was
* reached, or a deletion error.
*/
[[nodiscard]] TER
deletePseudoAccountCredentials(
ApplyView& view,
AccountID const& pseudoAcct,
std::uint16_t maxNodesToDelete,
beast::Journal j);
// Amendment and parameters checks for sfCredentialIDs field
NotTEC
checkFields(STTx const& tx, Rules const& rules, beast::Journal j);

View File

@@ -239,8 +239,13 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc
//------------------------------------------------------------------------------
/**
* Any transactors that call addEmptyHolding() in doApply must call
* canAddHolding() in preflight with the same View and Asset
* XRP and the issuer itself are always tesSUCCESS. Otherwise, after
* fixCleanup3_4_0, an existing trust line returns tecDUPLICATE without
* consulting issuer freeze or DefaultRipple; both still apply on the create
* path (DefaultRipple off is terNO_RIPPLE). canAddHolding() ignores existing
* holdings, so transactors that may create a holding in doApply should gate
* their preclaim call on it: after the amendment only when no holding
* exists, before it always.
*/
[[nodiscard]] TER
addEmptyHolding(

View File

@@ -38,6 +38,12 @@ enum class FreezeHandling { IgnoreFreeze, ZeroIfFrozen };
*/
enum class AuthHandling { IgnoreAuth, ZeroIfUnauthorized };
/**
* Controls whether the recipient owner-reserve check is enforced when
* auto-creating a trustline or MPToken during AMMWithdraw or AMMClawback.
*/
enum class ReserveHandling : bool { EnforceReserve, IgnoreReserve };
/**
* Controls whether to include the account's full spendable balance
*/
@@ -294,6 +300,14 @@ accountFunds(
AuthHandling authHandling,
beast::Journal j);
/**
* Returns the transfer fee as Rate based on the type of token
* @param view The ledger view
* @param asset The asset being transferred
*/
[[nodiscard]] Rate
transferRate(ReadView const& view, Asset const& asset);
/**
* Returns the transfer fee as Rate based on the type of token
* @param view The ledger view
@@ -311,6 +325,12 @@ transferRate(ReadView const& view, STAmount const& amount);
[[nodiscard]] TER
canAddHolding(ReadView const& view, Asset const& asset);
/**
* True if the account already holds this asset (or is the issuer / XRP).
*/
[[nodiscard]] bool
holdingExists(ReadView const& view, AccountID const& account, Asset const& asset);
[[nodiscard]] TER
addEmptyHolding(
ApplyViewContext ctx,

View File

@@ -10,6 +10,7 @@
#include <xrpl/protocol/TER.h>
#include <cstdint>
#include <expected>
#include <optional>
namespace xrpl {
@@ -44,6 +45,32 @@ assetsToSharesDeposit(SLE::const_ref vault, SLE::const_ref issuance, STAmount co
[[nodiscard]] std::optional<STAmount>
sharesToAssetsDeposit(SLE::const_ref vault, SLE::const_ref issuance, STAmount const& shares);
/**
* Adjusts a requested asset change (`delta`) to match the decimal scale of the
* updated total vault assets. This ensures `sfAssetsTotal`, `sfAssetsAvailable`,
* and the actual asset transfer change by the exact same representable amount.
*
* Rounding strategy:
* - Debits (withdrawals): Rounds down `|delta|` on the new scale to prevent
* paying out more than requested.
* - Credits (deposits): Floors the resulting total asset balance and returns the
* difference from the current total. This prevents crediting the vault with
* more assets than the user deposited.
*
* Key rules:
* - The returned magnitude never exceeds `|delta|`.
* - Returns `tecPRECISION_LOSS` if the change is smaller than 1 ULP of the target scale
* (prevents share operations when totals cannot change).
* - For integer assets (XRP, MPT), rounding is a no-op.
*
* @param vault The vault ledger entry.
* @param delta The requested signed change to sfAssetsTotal.
* @return The rounded, positive magnitude, or `tecPRECISION_LOSS` if the
* change is below representable precision.
*/
[[nodiscard]] std::expected<STAmount, TER>
clampToAssetsTotalScale(SLE::const_ref vault, STAmount const& delta);
/**
* Controls whether to truncate shares instead of rounding.
*/
@@ -59,33 +86,30 @@ enum class TruncateShares : bool { No = false, Yes = true };
enum class WaiveUnrealizedLoss : bool { No = false, Yes = true };
/**
* Returns the effective total of assets backing outstanding shares for the
* purposes of a withdrawal, i.e. sfAssetsTotal, discounted by sfLossUnrealized
* unless waived. This is the numerator used by both withdraw conversion
* helpers (assetsToSharesWithdraw and sharesToAssetsWithdraw) to compute the
* share/asset exchange rate.
* Returns the assets backing outstanding shares for a withdrawal:
* sfAssetsTotal minus sfLossUnrealized, or sfAssetsTotal alone when the
* unrealized loss is waived. Used by assetsToSharesWithdraw and
* sharesToAssetsWithdraw as the numerator of the share/asset exchange rate.
*
* @param vault The vault SLE.
* @param waive Whether to waive (i.e. not subtract) the vault's unrealized
* loss.
* @param waive Whether to skip subtracting the unrealized loss.
*/
[[nodiscard]] Number
assetsTotalForWithdrawal(SLE::const_ref vault, WaiveUnrealizedLoss waive);
/**
* Returns whether debiting `amount` from `total` — the current value of a
* vault's sfAssetsTotal or sfAssetsAvailable field — would canonicalize back
* to the exact same STAmount value it started at. This happens when a
* genuinely non-zero debit is dust relative to a `total` large enough to
* exceed STAmount's significant-digit precision: the shares still move, but
* the stored total doesn't change, which otherwise trips the ValidVault
* invariant after the fact instead of failing cleanly upfront.
* Returns true if debiting `amount` from `total` (the current value of a
* vault's sfAssetsTotal or sfAssetsAvailable) would canonicalize to the
* same STAmount value. This happens when `amount` is non-zero but too small
* to change the stored total at STAmount's precision. Shares would still
* move, so the ValidVault invariant would fail after apply; callers use
* this to reject the transaction upfront instead.
*
* @param asset The vault's underlying asset, used to canonicalize both sides
* the same way the ledger will when the field is stored.
* @param asset The vault's underlying asset, used to canonicalize both
* sides the same way the ledger will when the field is stored.
* @param total The field's current value.
* @param amount The amount to debit. A value of zero always returns false;
* that case is rejected separately and unconditionally.
* @param amount The amount to debit. Zero always returns false; that case
* is rejected separately.
*/
[[nodiscard]] bool
debitIsNonZeroDust(Asset const& asset, Number const& total, Number const& amount);

View File

@@ -92,6 +92,26 @@ enum class HashPrefix : std::uint32_t {
* Batch
*/
Batch = detail::makeHashPrefix('B', 'C', 'H'),
/**
* inner transaction to sign as the counterparty
*/
CounterpartyTxSign = detail::makeHashPrefix('C', 'P', 'T'),
/**
* inner transaction to multi-sign as the counterparty
*/
CounterpartyTxMultiSign = detail::makeHashPrefix('C', 'P', 'M'),
/**
* inner transaction to sign as the sponsor
*/
SponsorTxSign = detail::makeHashPrefix('S', 'P', 'N'),
/**
* inner transaction to multi-sign as the sponsor
*/
SponsorTxMultiSign = detail::makeHashPrefix('S', 'P', 'M'),
};
template <class Hasher>

View File

@@ -9,6 +9,7 @@
#include <cstdint>
#include <limits>
#include <optional>
#include <ostream>
#include <stdexcept>
#include <string>
@@ -174,4 +175,17 @@ mulRatio(MPTAmount const& amt, std::uint32_t num, std::uint32_t den, bool roundU
return MPTAmount(r.convert_to<MPTAmount::value_type>());
}
inline std::optional<MPTAmount>
tryMulRatio(MPTAmount const& amt, std::uint32_t num, std::uint32_t den, bool roundUp)
{
try
{
return mulRatio(amt, num, den, roundUp);
}
catch (std::overflow_error const&)
{
return std::nullopt;
}
}
} // namespace xrpl

View File

@@ -1,19 +0,0 @@
#pragma once
#include <xrpl/json/json_forwards.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TxMeta.h>
#include <memory>
namespace xrpl::rpc {
/**
* Adds common synthetic fields to transaction-related JSON responses
*/
/** @{ */
void
insertNFTSyntheticInJson(json::Value&, std::shared_ptr<STTx const> const&, TxMeta const&);
/** @} */
} // namespace xrpl::rpc

View File

@@ -12,6 +12,7 @@
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <limits>
namespace xrpl {
@@ -348,13 +349,24 @@ enum class VaultPhase : std::uint8_t {
Redemption,
};
/**
* Minimum gap between a closed-ended loan's final scheduled payment and the
* vault's RedemptionDate. LoanSet rejects a schedule whose final payment is
* fewer than this many seconds before RedemptionDate.
*/
constexpr std::uint32_t kLoanRedemptionBuffer = std::chrono::seconds{60}.count();
/**
* Bounds on the length of a closed-ended vault's Investment phase
* (RedemptionDate - SubscriptionDate). At vault creation the gap must satisfy
* kMinInvestmentPeriod <= gap < kMaxInvestmentPeriod.
*
* 180s is enough to originate a loan that uses the minimum payment interval
* and kLoanRedemptionBuffer after StartDate, which is strictly after
* SubscriptionDate. The interval and buffer need not be equal; only their
* sum plus one second must fit in this floor.
*/
constexpr std::uint32_t kMinInvestmentPeriod =
std::chrono::seconds{std::chrono::minutes{1}}.count();
constexpr std::uint32_t kMinInvestmentPeriod = std::chrono::seconds{180}.count();
// This is 946708560 seconds which 30 x 365.2425 days (the average length of a Gregorian year).
constexpr std::uint32_t kMaxInvestmentPeriod = std::chrono::seconds{std::chrono::years{30}}.count();
@@ -396,16 +408,6 @@ using TxID = uint256;
*/
constexpr std::uint16_t kMaxDeletableAmmTrustLines = 512;
/**
* The maximum number of owner-directory entries to walk when clearing
* credentials pinned to a pseudo-account, in a single transaction.
*
* The walk stops after this many entries whether or not each one turns out to
* be a credential, so a directory that also holds other objects yields fewer
* deletions per transaction.
*/
constexpr std::uint16_t kMaxDeletablePseudoAccountCredentials = 512;
/**
* The maximum length of a URI inside an Oracle
*/
@@ -543,6 +545,11 @@ constexpr std::size_t kEcClawbackProofLength = SECP256K1_COMPACT_CLAWBACK_PROOF_
*/
constexpr std::uint32_t kConfidentialFeeMultiplier = 9;
/**
* Maximum value a confidential MPT key epoch may reach.
*/
constexpr std::uint32_t kMaxKeyEpoch = std::numeric_limits<std::uint32_t>::max();
/**
* Compressed EC point prefix for even y-coordinate
*/

View File

@@ -5,6 +5,7 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/HashPrefix.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/SField.h>
@@ -13,6 +14,7 @@
#include <xrpl/protocol/SecretKey.h>
#include <xrpl/protocol/SeqProxy.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/Sign.h>
#include <xrpl/protocol/TxFormats.h>
#include <boost/container/flat_set.hpp>
@@ -105,14 +107,36 @@ public:
[[nodiscard]] json::Value
getJson(JsonOptions options, bool binary) const;
/**
* Sign the transaction as its account.
*
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
*/
void
sign(PublicKey const& publicKey, SecretKey const& secretKey);
/**
* Sign the transaction in one of its signature fields.
*
* The signature is bound to the role that made it, so it cannot be moved
* into another role.
*
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @param role The role signing the transaction.
* @param rules The current ledger rules.
*/
void
sign(
PublicKey const& publicKey,
SecretKey const& secretKey,
std::optional<std::reference_wrapper<SField const>> signatureTarget = {});
SignatureRole role,
Rules const& rules);
/**
* Check the signature.
*
* @param rules The current ledger rules.
* @return `true` if valid signature. If invalid, the error message string.
*/
@@ -120,7 +144,7 @@ public:
checkSign(Rules const& rules) const;
[[nodiscard]] std::expected<void, std::string>
checkBatchSign(Rules const& rules) const;
checkBatchSign() const;
// SQL Functions with metadata.
static std::string const&
@@ -162,28 +186,28 @@ public:
private:
/**
* Check the signature.
*
* @param rules The current ledger rules.
* @param sigObject Reference to object that contains the signature fields.
* Will be *this more often than not.
* @param role The role that made the signature in sigObject. Determines
* the signing prefix, which binds the signature to that role.
* @return `true` if valid signature. If invalid, the error message string.
*/
[[nodiscard]] std::expected<void, std::string>
checkSign(Rules const& rules, STObject const& sigObject) const;
checkSign(Rules const& rules, STObject const& sigObject, SignatureRole role) const;
[[nodiscard]] std::expected<void, std::string>
checkSingleSign(STObject const& sigObject) const;
checkSingleSign(STObject const& sigObject, HashPrefix prefix) const;
[[nodiscard]] std::expected<void, std::string>
checkMultiSign(Rules const& rules, STObject const& sigObject) const;
checkMultiSign(STObject const& sigObject, HashPrefix prefix) const;
[[nodiscard]] std::expected<void, std::string>
checkBatchSingleSign(STObject const& batchSigner, std::vector<uint256> const& txIds) const;
[[nodiscard]] std::expected<void, std::string>
checkBatchMultiSign(
STObject const& batchSigner,
Rules const& rules,
std::vector<uint256> const& txIds) const;
checkBatchMultiSign(STObject const& batchSigner, std::vector<uint256> const& txIds) const;
void
buildBatchTxns();

View File

@@ -4,13 +4,65 @@
#include <xrpl/protocol/HashPrefix.h>
#include <xrpl/protocol/KeyType.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/SecretKey.h>
#include <xrpl/protocol/Serializer.h>
#include <optional>
namespace xrpl {
/**
* The signature slots on a transaction.
*
* Each role signs different bytes, so a signature cannot be moved from the
* role that made it into another role. See signingPrefix.
*/
enum class SignatureRole {
/**
* The transaction's own signature, in sfTxnSignature or sfSigners.
*/
Transaction,
/**
* The counterparty's signature, in sfCounterpartySignature.
*/
Counterparty,
/**
* The sponsor's signature, in sfSponsorSignature.
*/
Sponsor
};
/**
* The field that holds this role's signature.
*
* @return The signature field, or nullptr for SignatureRole::Transaction,
* whose signature lives at the top level of the transaction.
*/
[[nodiscard]] SField const*
signatureField(SignatureRole role);
/**
* The role that signs into the given field.
*
* @return The role, or an unseated optional if the field does not hold a
* transaction signature.
*/
[[nodiscard]] std::optional<SignatureRole>
signatureRole(SField const& sigField);
/**
* The hash prefix that binds a transaction signature to the role that made it.
*
* @param role The role making the signature.
* @param multiSigning Whether the signature is a multi-signature.
* @param rules The current ledger rules.
*/
[[nodiscard]] HashPrefix
signingPrefix(SignatureRole role, bool multiSigning, Rules const& rules);
/**
* Sign an STObject
*
@@ -49,9 +101,12 @@ verify(
/**
* Return a Serializer suitable for computing a multisigning TxnSignature.
*
* @param prefix Prefix to insert before the serialized object. Get it from
* signingPrefix, so that the signature is bound to the role making it.
*/
Serializer
buildMultiSigningData(STObject const& obj, AccountID const& signingID);
buildMultiSigningData(STObject const& obj, AccountID const& signingID, HashPrefix prefix);
/**
* Break the multi-signing hash computation into 2 parts for optimization.
@@ -67,7 +122,7 @@ buildMultiSigningData(STObject const& obj, AccountID const& signingID);
* signer's unique data.
*/
Serializer
startMultiSigningData(STObject const& obj);
startMultiSigningData(STObject const& obj, HashPrefix prefix);
inline void
finishMultiSigningData(AccountID const& signingID, Serializer& s)

View File

@@ -16,10 +16,12 @@
// Keep it sorted in reverse chronological order.
XRPL_FEATURE(Cosign, Supported::No, VoteBehavior::DefaultNo)
XRPL_FIX (Cleanup3_5_0, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(ConfidentialMPTKeyRotation, Supported::No, VoteBehavior::DefaultNo)
XRPL_FIX (Cleanup3_4_0, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(Sponsor, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(BatchV1_1, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(LendingProtocolV1_1, Supported::No, VoteBehavior::DefaultNo)
XRPL_FEATURE(LendingProtocolV1_1, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(ConfidentialTransfer, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FIX (Cleanup3_3_0, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FIX (Cleanup3_2_0, Supported::Yes, VoteBehavior::DefaultNo)

View File

@@ -408,6 +408,8 @@ LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, mpt_issuance, ({
{sfReferenceHolding, SoeOptional},
{sfIssuerEncryptionKey, SoeOptional},
{sfAuditorEncryptionKey, SoeOptional},
{sfIssuerKeyEpoch, SoeOptional},
{sfAuditorKeyEpoch, SoeOptional},
{sfConfidentialOutstandingAmount, SoeDefault},
}))

View File

@@ -119,6 +119,10 @@ TYPED_SFIELD(sfRemainingOwnerCount, UINT32, 73)
TYPED_SFIELD(sfSponsorFlags, UINT32, 74)
TYPED_SFIELD(sfSubscriptionDate, UINT32, 75)
TYPED_SFIELD(sfRedemptionDate, UINT32, 76)
TYPED_SFIELD(sfIssuerKeyEpoch, UINT32, 77)
TYPED_SFIELD(sfAuditorKeyEpoch, UINT32, 78)
TYPED_SFIELD(sfIssuerKeyMirrorEpoch, UINT32, 79)
TYPED_SFIELD(sfAuditorKeyMirrorEpoch, UINT32, 80)
// 64-bit integers (common)
TYPED_SFIELD(sfIndexNext, UINT64, 1)

View File

@@ -351,6 +351,54 @@ public:
return this->sle_->isFieldPresent(sfAuditorEncryptionKey);
}
/**
* @brief Get sfIssuerKeyEpoch (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getIssuerKeyEpoch() const
{
if (hasIssuerKeyEpoch())
return this->sle_->at(sfIssuerKeyEpoch);
return std::nullopt;
}
/**
* @brief Check if sfIssuerKeyEpoch is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasIssuerKeyEpoch() const
{
return this->sle_->isFieldPresent(sfIssuerKeyEpoch);
}
/**
* @brief Get sfAuditorKeyEpoch (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getAuditorKeyEpoch() const
{
if (hasAuditorKeyEpoch())
return this->sle_->at(sfAuditorKeyEpoch);
return std::nullopt;
}
/**
* @brief Check if sfAuditorKeyEpoch is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasAuditorKeyEpoch() const
{
return this->sle_->isFieldPresent(sfAuditorKeyEpoch);
}
/**
* @brief Get sfConfidentialOutstandingAmount (SoeDefault)
* @return The field value, or std::nullopt if not present.
@@ -600,6 +648,28 @@ public:
return *this;
}
/**
* @brief Set sfIssuerKeyEpoch (SoeOptional)
* @return Reference to this builder for method chaining.
*/
MPTokenIssuanceBuilder&
setIssuerKeyEpoch(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfIssuerKeyEpoch] = value;
return *this;
}
/**
* @brief Set sfAuditorKeyEpoch (SoeOptional)
* @return Reference to this builder for method chaining.
*/
MPTokenIssuanceBuilder&
setAuditorKeyEpoch(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfAuditorKeyEpoch] = value;
return *this;
}
/**
* @brief Set sfConfidentialOutstandingAmount (SoeDefault)
* @return Reference to this builder for method chaining.

View File

@@ -267,6 +267,13 @@ public:
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx, std::uint32_t extraBaseFeeMultiplier);
// Exposed for invariant checks (e.g. ValidVault) that need to know which
// ledger entry actually pays a transaction's fee, distinguishing an
// ordinary sender, a delegate, and pre-funded vs. co-signed fee
// sponsorship.
static FeePayer
getFeePayer(ReadView const& view, STTx const& tx);
/* Do NOT define an invokePreflight function in a derived class.
Instead, define:
@@ -533,9 +540,6 @@ private:
std::pair<TER, XRPAmount>
reset(XRPAmount fee);
static FeePayer
getFeePayer(ReadView const& view, STTx const& tx);
TER
consumeSeqProxy(SLE::pointer const& sleAccount);
TER

View File

@@ -19,6 +19,11 @@ namespace xrpl {
* 1. If `LoanBroker.OwnerCount = 0` the `DirectoryNode` will have at most one
* node (the root), which will only hold entries for `RippleState` or
* `MPToken` objects.
* 2. Under featureLendingProtocolV1_1, an `ltLOAN_BROKER` may only be deleted
* by a `ttLOAN_BROKER_DELETE` transaction, and only when its pre-state
* `OwnerCount` is zero and its pre-state `DebtTotal` rounds to zero at the
* vault's `AssetsTotal` scale, as `LoanBrokerDelete::preclaim` requires.
* 3. At most one `ltLOAN_BROKER` may be deleted in a single transaction.
*
*/
class ValidLoanBroker
@@ -36,6 +41,15 @@ class ValidLoanBroker
// pseudo-accounts. Key is the brokerID / index. It will be used to find the
// LoanBroker object if brokerBefore and brokerAfter are nullptr
std::map<uint256, BrokerInfo> brokers_;
// The broker whose ledger entry was deleted by this transaction, if any.
// Only ttLOAN_BROKER_DELETE removes a broker, and it removes exactly one.
// This is the pre-transaction state, which is what LoanBrokerDelete::preclaim
// reads when it decides whether the broker may be deleted, so the deletion invariants inspect
// the same DebtTotal and OwnerCount that the transactor did.
SLE::const_pointer deletedBroker_ = nullptr;
// Set if visitEntry observes more than one ltLOAN_BROKER deletion in the
// same transaction. Enforced as its own invariant in finalize.
bool multipleBrokerDeletions_ = false;
// Collect all the modified trust lines. Their high and low accounts will be
// loaded to look for LoanBroker pseudo-accounts.
std::vector<SLE::const_pointer> lines_;

View File

@@ -15,9 +15,33 @@ namespace xrpl {
/**
* @brief Invariants: Loans are internally consistent
*
* 1. If `Loan.PaymentRemaining = 0` then `Loan.PrincipalOutstanding = 0`
* 1. If `Loan.PaymentRemaining = 0` then `Loan.PrincipalOutstanding = 0`.
* 2. A newly-created Loan against a closed-ended vault must satisfy
* `StartDate + PaymentInterval * PaymentRemaining < Vault.RedemptionDate`.
* 3. An `ltLOAN` may only be created by a `ttLOAN_SET` transaction.
* 4. Prior to `featureLendingProtocolV1_1`, the `lsfLoanOverpayment` flag on a
* Loan must not change. From `featureLendingProtocolV1_1` onward the same
* rule is enforced by `NoModifiedUnmodifiableFields`.
* 5. Under `featureLendingProtocolV1_1`:
* a. An `ltLOAN` may only be deleted by a `ttLOAN_DELETE` transaction.
* b. If `Loan.PaymentRemaining = 0` then `Loan.NextPaymentDueDate = 0`.
* c. The `lsfLoanImpaired` flag may only change through a `ttLOAN_MANAGE`
* or `ttLOAN_PAY` transaction.
* d. The `lsfLoanDefault` flag may only change through a `ttLOAN_MANAGE`
* transaction. Combined with `NoModifiedUnmodifiableFields`, which
* rejects any clearing of `lsfLoanDefault`, this makes the flag
* write-once: `ttLOAN_MANAGE` may set it, and no transaction may
* clear it.
* e. Interest due, computed as `TotalValueOutstanding -
* PrincipalOutstanding - ManagementFeeOutstanding`, must not be
* negative.
* f. A Loan must reference a live `ltLOAN_BROKER`, and that broker must
* reference a live `ltVAULT`.
* g. Post-conditions for the Loan paid down by a successful `ttLOAN_PAY`:
* `PaymentRemaining > 0` after: `PrincipalOutstanding` and
* `PaymentRemaining` strictly decrease; `NextPaymentDueDate`
* advances by N * `PaymentInterval`, N > 0.
* `PaymentRemaining == 0` after: pinned by checks 1 and 5b.
*
*/
class ValidLoan
@@ -25,6 +49,9 @@ class ValidLoan
// Pair is <before, after>. After is used for most of the checks, except
// those that check changed values.
std::vector<std::pair<SLE::const_pointer, SLE::const_pointer>> loans_;
// Loans removed from the ledger, in the same <before, after> form as loans_.
// Note that `after` holds the erased entry, so it is not null.
std::vector<std::pair<SLE::const_pointer, SLE::const_pointer>> deletedLoans_;
public:
void

View File

@@ -215,6 +215,13 @@ class ValidMPTTransfer
// Deleted MPToken
// MPToken key: true if MPTAuthorized is set
hash_map<uint256, bool> deletedAuthorized_;
// Every touched AccountRoot (not only pseudos):
// AccountID -> whether it was a pseudo-account BEFORE this transaction
// applied. Needed because a transaction may erase a pseudo-account and
// move MPT out of it in the same transaction; by finalize() time the
// view no longer shows it as a pseudo-account (or as existing at all).
// False entries freeze the pre-tx classification for touched non-pseudos.
hash_map<AccountID, bool> pseudoAccountsBefore_;
public:
/**

View File

@@ -48,7 +48,10 @@ namespace xrpl {
* vault phase is Investment
*
* Immutability of VaultKind, SubscriptionDate and RedemptionDate is enforced
* by NoModifiedUnmodifiableFields (see InvariantCheck.cpp).
* by NoModifiedUnmodifiableFields (see InvariantCheck.cpp). From
* featureLendingProtocolV1_1 onwards, immutability of the vault's Asset,
* pseudo-account and ShareMPTID is likewise enforced by
* NoModifiedUnmodifiableFields; prior to that amendment it is checked here.
*/
class ValidVault
{
@@ -128,20 +131,57 @@ private:
deltaAssets(AccountID const& id) const;
/**
* @brief Return the vault-asset delta for the transaction's sending
* account, adjusted for the fee.
* @brief Return the AccountRoot whose XRP balance actually absorbed a
* transaction's fee, if any.
*
* Calls @c deltaAssets for @c tx[sfAccount] and, for non-delegated XRP
* transactions, adds the consumed fee back so the invariant sees the net
* asset movement rather than the fee-reduced balance change.
* Mirrors @c Transactor::getFeePayer, but resolves to @c std::nullopt for
* a pre-funded sponsorship: that fee is drawn from the @c ltSponsorship
* object's @c sfFeeAmount, never from the sponsor's own AccountRoot, so
* there is no balance to add back there.
*
* @param tx The transaction being applied.
* @param fee Fee charged by this transaction.
* @param view Read-only view of the ledger after the transaction.
* @param tx The transaction being applied.
* @return The fee-paying AccountRoot's id, or @c std::nullopt when the
* fee was not drawn from any AccountRoot balance.
*/
[[nodiscard]] static std::optional<AccountID>
feePayerAccountRoot(ReadView const& view, STTx const& tx);
/**
* @brief Return the vault-asset delta for a party inspected as a
* withdrawal/deposit counterparty, adjusted for the fee.
*
* Calls @c deltaAssets for @p id and, for XRP transactions, adds the
* consumed fee back only when @p id is the AccountRoot that actually
* paid it (per @c feePayerAccountRoot) -- so the invariant sees the net
* asset movement rather than a fee-reduced balance change, regardless of
* whether @p id is the sender, a distinct destination, a delegate, or a
* co-signed fee sponsor. Post-@c fixCleanup3_4_0, any resulting
* economically-zero delta is always normalized to absence.
*
* Pre-@c fixCleanup3_4_0 this replicates the legacy behaviour exactly:
* only @c tx[sfAccount] could ever receive a fee correction (and only
* when it was itself, per @c STTx::getFeePayerID, the fee payer). After
* that sender-only correction a zero delta is collapsed to absence; if
* the correction does not apply, a present-zero delta is kept as-is.
*
* @param view Read-only view of the ledger after the transaction.
* @param id Account being inspected as sender or destination.
* @param tx The transaction being applied.
* @param fee Fee charged by this transaction.
* @param fix340Enabled Whether @c fixCleanup3_4_0 is enabled, as already
* determined once by @c finalize.
* @return The fee-adjusted delta, or @c std::nullopt if the net delta is
* zero or the account entry was not touched.
* zero (always post-amendment; pre-amendment only after the
* sender-only fee correction) or the entry was not touched.
*/
[[nodiscard]] std::optional<DeltaInfo>
deltaAssetsTxAccount(STTx const& tx, XRPAmount fee) const;
deltaAssetsForParty(
ReadView const& view,
AccountID const& id,
STTx const& tx,
XRPAmount fee,
bool fix340Enabled) const;
/**
* @brief Return the vault-share balance-change delta for an account.
@@ -171,8 +211,8 @@ private:
*
* For a closed-ended vault, a loan may only be originated while the vault is in the Investment
* phase (strictly past @c SubscriptionDate and before @c RedemptionDate). Open-ended vaults (@c
* NoPhase) are unaffected. The complementary maturity bound (final payment strictly precedes @c
* RedemptionDate) is enforced by @c ValidLoan.
* NoPhase) are unaffected. The complementary maturity bound (final payment precedes @c
* RedemptionDate by at least @c kLoanRedemptionBuffer) is enforced by @c ValidLoan.
*/
[[nodiscard]] bool
finalizeLoanSet(ReadView const& view, beast::Journal const& j) const;

View File

@@ -109,6 +109,11 @@ public:
* @param lpTokens current LPT balance
* @param lpTokensWithdraw amount of tokens to withdraw
* @param tfee trading fee in basis points
* @param freezeHandling whether a frozen balance is reported as zero
* @param authHandling whether an unauthorized MPT balance is reported as
* zero
* @param reserveHandling whether the recipient owner-reserve check is
* enforced when a trustline or MPToken has to be auto-created
* @param withdrawAll if withdrawing all lptokens
* @param priorBalance balance before fees
* @return
@@ -128,6 +133,7 @@ public:
std::uint16_t tfee,
FreezeHandling freezeHandling,
AuthHandling authHandling,
ReserveHandling reserveHandling,
WithdrawAll withdrawAll,
XRPAmount const& priorBalance,
beast::Journal const& journal);
@@ -150,6 +156,11 @@ public:
* @param lpTokensAMMBalance current AMM LPT balance
* @param lpTokensWithdraw amount of lptokens to withdraw
* @param tfee trading fee in basis points
* @param freezeHandling whether a frozen balance is reported as zero
* @param authHandling whether an unauthorized MPT balance is reported as
* zero
* @param reserveHandling whether the recipient owner-reserve check is
* enforced when a trustline or MPToken has to be auto-created
* @param withdrawAll if withdraw all lptokens
* @param priorBalance balance before fees
* @return
@@ -169,6 +180,7 @@ public:
std::uint16_t tfee,
FreezeHandling freezeHandling,
AuthHandling authHandling,
ReserveHandling reserveHandling,
WithdrawAll withdrawAll,
XRPAmount const& priorBalance,
beast::Journal const& journal);