Compare commits

...
Author SHA1 Message Date
Mayukha Vadari fa43871c5e Document assetsTotalScale() and minimumBrokerCover() 2026-10-05 15:46:19 +01:00
Mayukha Vadari 29fff28b39 refactor: Move getAssetsTotalScale and minimumBrokerCover onto VaultEntry
Both helpers lived in LendingHelpers.h but only read the Vault entry.
Replace them with the const members VaultEntry::assetsTotalScale() and
VaultEntry::minimumBrokerCover(debtTotal, coverRateMinimum). The
bodies stay inline, now in VaultEntry.h.
2026-10-05 15:46:02 +01:00
Mayukha Vadari 0a0c761543 Document assetsTotalForWithdrawal() return value 2026-10-05 15:45:54 +01:00
Mayukha Vadari ac32719761 refactor: Move assetsTotalForWithdrawal onto VaultEntry
Replace the free assetsTotalForWithdrawal(VaultEntryR const&, waive)
helper with the const member VaultEntry::assetsTotalForWithdrawal().
VaultEntry.h forward-declares WaiveUnrealizedLoss, because
VaultHelpers.h defines it and includes VaultEntry.h; the body is in
VaultEntry.cpp, which sees the full definition.
2026-10-05 15:45:43 +01:00
Mayukha Vadari fe6ca095bf refactor: Move clampToAssetsTotalScale onto VaultEntry
Replace the free clampToAssetsTotalScale(VaultEntryR const&, ...)
helper with the const member VaultEntry::clampToAssetsTotalScale().
The body moves to VaultEntry.cpp unchanged. Callers now call
vault.clampToAssetsTotalScale(delta).
2026-10-05 15:45:38 +01:00
Mayukha Vadari 4d30c9a770 Document phase() return value 2026-10-05 15:45:30 +01:00
Mayukha Vadari bf61b4956d refactor: Move getVaultPhase onto VaultEntry
Replace the entry overload of getVaultPhase with the const member
VaultEntry::phase(), which uses the entry's own view as the clock. The
raw-fields overload stays a free function, because VaultInvariant calls
it without an entry; the member calls it. Callers now call
vault.phase().
2026-10-05 15:45:23 +01:00
Mayukha Vadari 991fff021c Document kind() return value 2026-10-05 15:44:56 +01:00
Mayukha Vadari 530e0e894a refactor: Move getVaultKind onto VaultEntry
Replace the free getVaultKind(VaultEntryR const&) helper with the
const member VaultEntry::kind(). The STTx overload stays a free
function. Callers now call vault.kind().
2026-10-05 14:38:55 +01:00
Mayukha Vadari 345a870b0d Include Keylet.h in the SLEBase test
(cherry picked from commit 3cca9d3266)
2026-10-05 14:38:52 +01:00
Mayukha Vadari 03438164d8 test: Drop extern template declarations from SLEBase test
An explicit instantiation declaration stops clang from emitting the
inline constructors of the header-only entries at -O0, so the debug
builds fail to link xrpl_tests (undefined AccountRootEntry constructors).
Naming each class in a static_assert already instantiates its definition,
which is all the exhaustiveness check needs.

(cherry picked from commit 611415e1f9)
2026-10-05 14:38:52 +01:00
Mayukha Vadari a382d1a70f Drop unused include from LoanTestBase.h 2026-10-05 13:45:16 +01:00
Mayukha Vadari 71058accef refactor: Move getVaultVersion onto VaultEntry
Replace the free getVaultVersion(VaultEntryR const&) helper with the
const member VaultEntry::version(). The body moves to the new
src/libxrpl/ledger/entries/VaultEntry.cpp, which explicitly
instantiates VaultEntry for ReadView and ApplyView. Callers now call
vault.version().
2026-10-05 13:45:16 +01:00
Mayukha Vadari 8a1bfc322a test: Declare, not define, entry instantiations in SLEBase test
The exhaustive per-entry-type check in the SLEBase gtest used explicit
instantiation definitions. Once an entry class gets out-of-line member
bodies in its own .cpp (with the explicit instantiation definition
there), the test would instantiate the class a second time, and MSVC
rejects it with C4661 because those member bodies are not visible in
the test translation unit. Explicit instantiation declarations still
instantiate the class definition, which is all the check needs.

(cherry picked from commit 8b07abc3cd)
2026-10-05 13:45:16 +01:00
Mayukha Vadari f66808c64a refactor: Use VaultEntry everywhere
Replace raw Vault SLE pointers with VaultEntryR/VaultEntryW in the
ledger helpers, the Vault transactors, and the lending transactors.
Helpers that take a Vault SLE (getVaultVersion, getVaultKind,
getVaultPhase, clampToAssetsTotalScale, the share/asset conversions,
getAssetsTotalScale, minimumBrokerCover, the lending accounting
dispatchers, and the transactor-local helpers) now take
VaultEntryR const& or VaultEntryW& and stay free functions.

The compiler now enforces the entry type and read/write access at
every Vault call site. Invariants and tests get only the minimal
edits the new signatures force: they wrap their SLE in VaultEntryR.

Pure refactor with no behavior change.
2026-10-05 13:33:07 +01:00
35 changed files with 455 additions and 343 deletions

No files matched your search

+122
View File
@@ -1,17 +1,29 @@
#pragma once
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/SeqProxy.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/Units.h>
#include <expected>
namespace xrpl {
// Defined in VaultHelpers.h, which includes this header.
enum class WaiveUnrealizedLoss : bool;
template <typename ViewT>
class VaultEntry : public SLEBase<ViewT, ltVAULT>
{
@@ -38,6 +50,116 @@ public:
: Base(keylet::vault(vaultID), view, j)
{
}
/**
* Resolves the Vault's LEVersion, the single point every accounting touch
* point should call to determine which recognition model (instant
* interest recognition vs. cash-basis) the Vault uses. Vaults created
* before featureLendingProtocolV1_1 activated never have sfLEVersion set,
* which resolves here to VaultVersion::Legacy.
*
* @return The Vault's LEVersion, or VaultVersion::Legacy if the field is
* absent.
*/
[[nodiscard]] VaultVersion
version() const;
/**
* Resolves the Vault's VaultKind. Returns VaultKind::ClosedEnded when
* sfVaultKind is present and equal to that value; anything else
* (including an absent field or an unrecognised value) is treated as
* VaultKind::OpenEnded.
*
* @return The Vault's VaultKind.
*/
[[nodiscard]] VaultKind
kind() const;
/**
* Returns the current lifecycle phase of the vault. Open-ended vaults are
* always NoPhase. For closed-ended vaults the phase is derived from the
* parent close time of the entry's view and the vault's immutable
* SubscriptionDate and RedemptionDate.
*
* @return The vault's current VaultPhase.
*/
[[nodiscard]] VaultPhase
phase() const;
/**
* 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 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(STAmount const& delta) const;
/**
* 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 waive Whether to skip subtracting the unrealized loss.
* @return The assets backing outstanding shares for a withdrawal.
*/
[[nodiscard]] Number
assetsTotalForWithdrawal(WaiveUnrealizedLoss waive) const;
/**
* Returns the scale of the vault's sfAssetsTotal for its asset, or
* Number::kMinExponent - 1 if the entry does not exist.
*
* @return The scale of the vault's sfAssetsTotal, or Number::kMinExponent
* - 1 if the entry does not exist.
*/
[[nodiscard]] int
assetsTotalScale() const
{
if (!*this)
return Number::kMinExponent - 1; // LCOV_EXCL_LINE
return scale((*this)->at(sfAssetsTotal), (*this)->at(sfAsset));
}
/**
* Computes the minimum required broker cover, rounded consistently.
* DebtTotal is a broker-level aggregate maintained at vault scale, so the
* rounding must also use vault scale — never an individual loan's scale.
*
* @param debtTotal The broker's total outstanding debt, at vault scale.
* @param coverRateMinimum The minimum cover rate to apply to debtTotal.
* @return The minimum required broker cover, rounded up to vault scale.
*/
[[nodiscard]] Number
minimumBrokerCover(Number const& debtTotal, TenthBips32 coverRateMinimum) const
{
XRPL_ASSERT(
*this && (*this)->getType() == ltVAULT,
"xrpl::VaultEntry::minimumBrokerCover : valid Vault sle");
NumberRoundModeGuard const mg(Number::RoundingMode::Upward);
return roundToAsset(
(*this)->at(sfAsset),
tenthBipsOfValue(debtTotal, coverRateMinimum),
assetsTotalScale());
}
};
using VaultEntryR = VaultEntry<ReadView>;
+6 -29
View File
@@ -7,10 +7,10 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/LedgerFormats.h> // IWYU pragma: keep
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
@@ -254,29 +254,6 @@ adjustImpreciseNumber(
value = 0;
}
inline int
getAssetsTotalScale(SLE::ConstRef vaultSle)
{
if (!vaultSle)
return Number::kMinExponent - 1; // LCOV_EXCL_LINE
return scale(vaultSle->at(sfAssetsTotal), vaultSle->at(sfAsset));
}
// Compute the minimum required broker cover, rounded consistently.
// DebtTotal is a broker-level aggregate maintained at vault scale, so the
// rounding must also use vault scale — never an individual loan's scale.
inline Number
minimumBrokerCover(Number const& debtTotal, TenthBips32 coverRateMinimum, SLE::ConstRef vaultSle)
{
XRPL_ASSERT(
vaultSle && vaultSle->getType() == ltVAULT, "xrpl::minimumBrokerCover : valid Vault sle");
NumberRoundModeGuard const mg(Number::RoundingMode::Upward);
return roundToAsset(
vaultSle->at(sfAsset),
tenthBipsOfValue(debtTotal, coverRateMinimum),
getAssetsTotalScale(vaultSle));
}
TER
checkLoanGuards(
Asset const& vaultAsset,
@@ -380,26 +357,26 @@ loanPaymentDeltas(LoanPaymentParts const& parts);
} // namespace cash_basis
// Public dispatchers: pick cash_basis:: if featureLendingProtocolV1_1 is
// enabled AND the Vault's LEVersion (VaultHelpers::getVaultVersion) is
// enabled AND the Vault's LEVersion (VaultEntry::version) is
// VaultVersion::CashBasis, else instant_recognition::. These are the only entry points
// transactors call.
AccountingDeltas
loanOriginationDeltas(
SLE::ConstRef vaultSle,
VaultEntryR const& vaultSle,
Number const& principalRequested,
Number const& interestDue);
bool
loanOriginationExceedsVaultMaximum(
SLE::ConstRef vaultSle,
VaultEntryR const& vaultSle,
Number const& vaultTotal,
Number const& interestDue);
Number
loanVaultExposure(SLE::ConstRef vaultSle, SLE::ConstRef loanSle);
loanVaultExposure(VaultEntryR const& vaultSle, SLE::ConstRef loanSle);
AccountingDeltas
loanPaymentDeltas(SLE::ConstRef vaultSle, LoanPaymentParts const& parts);
loanPaymentDeltas(VaultEntryR const& vaultSle, LoanPaymentParts const& parts);
namespace detail {
// These classes and functions should only be accessed by LendingHelper
+8 -83
View File
@@ -2,6 +2,7 @@
#include <xrpl/basics/Number.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Protocol.h>
@@ -10,7 +11,6 @@
#include <xrpl/protocol/TER.h>
#include <cstdint>
#include <expected>
#include <optional>
namespace xrpl {
@@ -29,7 +29,7 @@ class STTx;
* @return The number of shares, or nullopt on error.
*/
[[nodiscard]] std::optional<STAmount>
assetsToSharesDeposit(SLE::ConstRef vault, SLE::ConstRef issuance, STAmount const& assets);
assetsToSharesDeposit(VaultEntryR const& vault, SLE::ConstRef issuance, STAmount const& assets);
/**
* From the perspective of a vault, return the number of assets to take from
@@ -43,33 +43,7 @@ assetsToSharesDeposit(SLE::ConstRef vault, SLE::ConstRef issuance, STAmount cons
* @return The number of assets, or nullopt on error.
*/
[[nodiscard]] std::optional<STAmount>
sharesToAssetsDeposit(SLE::ConstRef vault, SLE::ConstRef 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::ConstRef vault, STAmount const& delta);
sharesToAssetsDeposit(VaultEntryR const& vault, SLE::ConstRef issuance, STAmount const& shares);
/**
* Controls whether to truncate shares instead of rounding.
@@ -85,18 +59,6 @@ enum class TruncateShares : bool { No = false, Yes = true };
*/
enum class WaiveUnrealizedLoss : bool { No = false, Yes = true };
/**
* 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 skip subtracting the unrealized loss.
*/
[[nodiscard]] Number
assetsTotalForWithdrawal(SLE::ConstRef vault, WaiveUnrealizedLoss waive);
/**
* Returns true if debiting `amount` from `total` (the current value of a
* vault's sfAssetsTotal or sfAssetsAvailable) would canonicalize to the
@@ -131,7 +93,7 @@ debitIsNonZeroDust(Asset const& asset, Number const& total, Number const& amount
*/
[[nodiscard]] std::optional<STAmount>
assetsToSharesWithdraw(
SLE::ConstRef vault,
VaultEntryR const& vault,
SLE::ConstRef issuance,
STAmount const& assets,
TruncateShares truncate = TruncateShares::No,
@@ -152,7 +114,7 @@ assetsToSharesWithdraw(
*/
[[nodiscard]] std::optional<STAmount>
sharesToAssetsWithdraw(
SLE::ConstRef vault,
VaultEntryR const& vault,
SLE::ConstRef issuance,
STAmount const& shares,
WaiveUnrealizedLoss waive = WaiveUnrealizedLoss::No);
@@ -170,35 +132,10 @@ sharesToAssetsWithdraw(
[[nodiscard]] bool
isSoleShareholder(ReadView const& view, AccountID const& account, SLE::ConstRef issuance);
/**
* Resolves a Vault's LEVersion, the single point every accounting touch
* point should call to determine which recognition model (instant interest
* recognition vs. cash-basis) a Vault uses. Vaults created before featureLendingProtocolV1_1
* activated never have sfLEVersion set, which resolves here to
* VaultVersion::Legacy.
*
* @param vault The vault SLE.
*
* @return The Vault's LEVersion, or VaultVersion::Legacy if the field is
* absent.
*/
[[nodiscard]] VaultVersion
getVaultVersion(SLE::ConstRef vault);
/**
* Resolves the VaultKind of a vault SLE. Returns VaultKind::ClosedEnded when
* sfVaultKind is present and equal to that value; anything else (including an
* absent field or an unrecognised value) is treated as VaultKind::OpenEnded.
*
* @param vault The vault SLE.
*/
[[nodiscard]] VaultKind
getVaultKind(SLE::ConstRef vault);
/**
* Reads sfVaultKind from a transaction. An absent field resolves to
* VaultKind::OpenEnded (matching the on-ledger default); any unrecognised
* value is also treated as VaultKind::OpenEnded, mirroring the SLE overload.
* value is also treated as VaultKind::OpenEnded, mirroring VaultEntry::kind().
* Callers that need to reject out-of-range values (e.g. preflight) should
* gate on isValidVaultKind() first.
*
@@ -231,23 +168,11 @@ isValidVaultKind(STTx const& tx);
isValidClosedEndedGap(std::uint32_t sub, std::uint32_t red);
/**
* Returns the current lifecycle phase of a vault. Open-ended
* vaults are always NoPhase. For closed-ended vaults the phase is derived
* from the parent ledger close time and the vault's immutable
* SubscriptionDate and RedemptionDate.
*
* @param view The ledger view whose parent close time is used as the clock.
* @param vault The vault SLE.
*/
[[nodiscard]] VaultPhase
getVaultPhase(ReadView const& view, SLE::ConstRef vault);
/**
* Raw-fields overload of getVaultPhase. Derives the phase from an already
* Raw-fields variant of VaultEntry::phase(). Derives the phase from an already
* decomposed vault snapshot: an absent or non-ClosedEnded @p vaultKind
* resolves to VaultPhase::NoPhase; otherwise the phase is computed from
* @p subscriptionDate and @p redemptionDate against the view's parent
* close time using the same boundary semantics as the SLE overload
* close time using the same boundary semantics as VaultEntry::phase()
* (Subscription is inclusive of now == SubscriptionDate; Investment starts
* strictly after).
*
@@ -4,6 +4,7 @@
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
@@ -44,7 +45,7 @@ public:
ApplyView& view,
SLE::Ref loanSle,
SLE::Ref brokerSle,
SLE::Ref vaultSle,
VaultEntryW& vaultSle,
Asset const& vaultAsset,
beast::Journal j);
@@ -55,7 +56,7 @@ public:
impairLoan(
ApplyView& view,
SLE::Ref loanSle,
SLE::Ref vaultSle,
VaultEntryW& vaultSle,
Asset const& vaultAsset,
beast::Journal j);
@@ -66,7 +67,7 @@ public:
unimpairLoan(
ApplyView& view,
SLE::Ref loanSle,
SLE::Ref vaultSle,
VaultEntryW& vaultSle,
Asset const& vaultAsset,
beast::Journal j);
@@ -3,6 +3,7 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STTx.h>
@@ -48,7 +49,7 @@ public:
private:
std::expected<std::pair<STAmount, STAmount>, TER>
assetsToClawback(
SLE::Ref vault,
VaultEntryW& vault,
SLE::ConstRef sleShareIssuance,
AccountID const& holder,
STAmount const& clawbackAmount);
+3 -2
View File
@@ -9,6 +9,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
@@ -127,8 +128,8 @@ isVaultPseudoAccountFrozenForIssuance(
if (!mptIssuer->isFieldPresent(sfVaultID))
return false; // not a Vault pseudo-account, common case
auto const vault = view.read(keylet::vault(mptIssuer->getFieldH256(sfVaultID)));
if (vault == nullptr)
VaultEntryR const vault(mptIssuer->getFieldH256(sfVaultID), view);
if (!vault)
{ // LCOV_EXCL_START
UNREACHABLE("xrpl::isVaultPseudoAccountFrozen : null vault");
return false;
+140
View File
@@ -0,0 +1,140 @@
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/basics/Number.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STNumber.h> // IWYU pragma: keep
#include <xrpl/protocol/TER.h>
#include <expected>
#include <utility>
namespace xrpl {
template <typename ViewT>
VaultVersion
VaultEntry<ViewT>::version() const
{
XRPL_ASSERT(
*this && (*this)->getType() == ltVAULT, "xrpl::VaultEntry::version : valid Vault sle");
if (!(*this)->isFieldPresent(sfLEVersion))
return VaultVersion::Legacy;
auto const version = (*this)->at(sfLEVersion);
if (version > std::to_underlying(VaultVersion::CashBasis))
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::VaultEntry::version : invalid vault version");
return VaultVersion::Legacy;
// LCOV_EXCL_STOP
}
return static_cast<VaultVersion>(version);
}
template <typename ViewT>
VaultKind
VaultEntry<ViewT>::kind() const
{
XRPL_ASSERT(*this && (*this)->getType() == ltVAULT, "xrpl::VaultEntry::kind : valid Vault sle");
auto const vaultKind = (*this)->at(~sfVaultKind);
if (vaultKind && *vaultKind == std::to_underlying(VaultKind::ClosedEnded))
return VaultKind::ClosedEnded;
return VaultKind::OpenEnded;
}
template <typename ViewT>
VaultPhase
VaultEntry<ViewT>::phase() const
{
XRPL_ASSERT(
*this && (*this)->getType() == ltVAULT, "xrpl::VaultEntry::phase : valid Vault sle");
return getVaultPhase(
this->readView(),
(**this)[~sfVaultKind],
(**this)[~sfSubscriptionDate],
(**this)[~sfRedemptionDate]);
}
template <typename ViewT>
std::expected<STAmount, TER>
VaultEntry<ViewT>::clampToAssetsTotalScale(STAmount const& delta) const
{
XRPL_ASSERT(
delta.asset() == (*this)->at(sfAsset),
"xrpl::VaultEntry::clampToAssetsTotalScale : delta and vault asset match");
Asset const asset = (*this)->at(sfAsset);
STAmount magnitude = delta.negative() ? -delta : delta;
if (asset.integral())
{
return magnitude;
}
Number const assetsTotal = (*this)->at(sfAssetsTotal);
// Calculate the scale after applying the delta using ToNearest rounding.
// This aligns the delta with scale checks used by vault invariants.
int const postScale = [&] {
NumberRoundModeGuard const rg(Number::RoundingMode::ToNearest);
return scale(assetsTotal + delta, asset);
}();
STAmount actualDelta;
if (delta.negative())
{
// For withdrawals (debits), floor the magnitude to the target scale
// to ensure exact grid alignment without paying out extra assets.
actualDelta = roundToScale(magnitude, postScale, Number::RoundingMode::Downward);
}
else
{
// For deposits (credits), derive actualDelta from the floored posterior total.
// This prevents grid alignment issues from crediting the vault more than deposited.
//
// Sum using Downward rounding so intermediate precision doesn't round up
// and exceed the original requested amount.
Number const posterior = [&] {
NumberRoundModeGuard const rg(Number::RoundingMode::Downward);
return assetsTotal + magnitude;
}();
Number const roundedPosterior =
roundToAsset(asset, posterior, postScale, Number::RoundingMode::Downward);
actualDelta = STAmount{asset, roundedPosterior - assetsTotal};
}
XRPL_ASSERT(
abs(actualDelta) <= abs(delta),
"xrpl::VaultEntry::clampToAssetsTotalScale : actual delta smaller or equal to calculated "
"delta");
// Reject changes below scale precision (1 ULP) to prevent share balance changes
// without corresponding asset movements.
if (actualDelta <= beast::kZero)
return std::unexpected(tecPRECISION_LOSS);
return actualDelta;
}
template <typename ViewT>
Number
VaultEntry<ViewT>::assetsTotalForWithdrawal(WaiveUnrealizedLoss waive) const
{
Number assetTotal = (*this)->at(sfAssetsTotal);
if (waive == WaiveUnrealizedLoss::No)
assetTotal -= (*this)->at(sfLossUnrealized);
return assetTotal;
}
template class VaultEntry<ReadView>;
template class VaultEntry<ApplyView>;
} // namespace xrpl
@@ -9,6 +9,7 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
@@ -103,7 +104,7 @@ getLoanDefaultFreezeExemptAccounts(ReadView const& view, STTx const& tx)
if (!brokerSle)
return std::nullopt; // LCOV_EXCL_LINE
auto const vaultSle = view.read(keylet::vault(brokerSle->at(sfVaultID)));
VaultEntryR const vaultSle(brokerSle->at(sfVaultID), view);
if (!vaultSle)
return std::nullopt; // LCOV_EXCL_LINE
@@ -253,16 +254,16 @@ namespace {
// VaultVersion::CashBasis). Vaults created before activation keep instant
// interest recognition forever, even after the amendment later turns on.
bool
cashBasisEnabled(SLE::ConstRef vaultSle)
cashBasisEnabled(VaultEntryR const& vaultSle)
{
return getVaultVersion(vaultSle) == VaultVersion::CashBasis;
return vaultSle.version() == VaultVersion::CashBasis;
}
} // namespace
AccountingDeltas
loanOriginationDeltas(
SLE::ConstRef vaultSle,
VaultEntryR const& vaultSle,
Number const& principalRequested,
Number const& interestDue)
{
@@ -273,7 +274,7 @@ loanOriginationDeltas(
bool
loanOriginationExceedsVaultMaximum(
SLE::ConstRef vaultSle,
VaultEntryR const& vaultSle,
Number const& vaultTotal,
Number const& interestDue)
{
@@ -288,14 +289,14 @@ loanOriginationExceedsVaultMaximum(
}
Number
loanVaultExposure(SLE::ConstRef vaultSle, SLE::ConstRef loanSle)
loanVaultExposure(VaultEntryR const& vaultSle, SLE::ConstRef loanSle)
{
return cashBasisEnabled(vaultSle) ? cash_basis::loanVaultExposure(loanSle)
: instant_recognition::loanVaultExposure(loanSle);
}
AccountingDeltas
loanPaymentDeltas(SLE::ConstRef vaultSle, LoanPaymentParts const& parts)
loanPaymentDeltas(VaultEntryR const& vaultSle, LoanPaymentParts const& parts)
{
return cashBasisEnabled(vaultSle) ? cash_basis::loanPaymentDeltas(parts)
: instant_recognition::loanPaymentDeltas(parts);
@@ -7,6 +7,7 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
@@ -421,7 +422,7 @@ requireAuth(
if (sleIssuer->isFieldPresent(sfVaultID))
{
auto const sleVault = view.read(keylet::vault(sleIssuer->getFieldH256(sfVaultID)));
VaultEntryR const sleVault(sleIssuer->getFieldH256(sfVaultID), view);
if (!sleVault)
return tefINTERNAL; // LCOV_EXCL_LINE
+7 -109
View File
@@ -1,10 +1,10 @@
#include <xrpl/ledger/helpers/VaultHelpers.h>
#include <xrpl/basics/Number.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
@@ -18,14 +18,13 @@
#include <xrpl/protocol/TER.h>
#include <cstdint>
#include <expected>
#include <optional>
#include <utility>
namespace xrpl {
[[nodiscard]] std::optional<STAmount>
assetsToSharesDeposit(SLE::ConstRef vault, SLE::ConstRef issuance, STAmount const& assets)
assetsToSharesDeposit(VaultEntryR const& vault, SLE::ConstRef issuance, STAmount const& assets)
{
XRPL_ASSERT(!assets.negative(), "xrpl::assetsToSharesDeposit : non-negative assets");
XRPL_ASSERT(
@@ -49,7 +48,7 @@ assetsToSharesDeposit(SLE::ConstRef vault, SLE::ConstRef issuance, STAmount cons
}
[[nodiscard]] std::optional<STAmount>
sharesToAssetsDeposit(SLE::ConstRef vault, SLE::ConstRef issuance, STAmount const& shares)
sharesToAssetsDeposit(VaultEntryR const& vault, SLE::ConstRef issuance, STAmount const& shares)
{
XRPL_ASSERT(!shares.negative(), "xrpl::sharesToAssetsDeposit : non-negative shares");
XRPL_ASSERT(
@@ -71,74 +70,6 @@ sharesToAssetsDeposit(SLE::ConstRef vault, SLE::ConstRef issuance, STAmount cons
return assets;
}
[[nodiscard]] std::expected<STAmount, TER>
clampToAssetsTotalScale(SLE::ConstRef vault, STAmount const& delta)
{
XRPL_ASSERT(
delta.asset() == vault->at(sfAsset),
"xrpl::clampToAssetsTotalScale : delta and vault asset match");
Asset const asset = vault->at(sfAsset);
STAmount magnitude = delta.negative() ? -delta : delta;
if (asset.integral())
{
return magnitude;
}
Number const assetsTotal = vault->at(sfAssetsTotal);
// Calculate the scale after applying the delta using ToNearest rounding.
// This aligns the delta with scale checks used by vault invariants.
int const postScale = [&] {
NumberRoundModeGuard const rg(Number::RoundingMode::ToNearest);
return scale(assetsTotal + delta, asset);
}();
STAmount actualDelta;
if (delta.negative())
{
// For withdrawals (debits), floor the magnitude to the target scale
// to ensure exact grid alignment without paying out extra assets.
actualDelta = roundToScale(magnitude, postScale, Number::RoundingMode::Downward);
}
else
{
// For deposits (credits), derive actualDelta from the floored posterior total.
// This prevents grid alignment issues from crediting the vault more than deposited.
//
// Sum using Downward rounding so intermediate precision doesn't round up
// and exceed the original requested amount.
Number const posterior = [&] {
NumberRoundModeGuard const rg(Number::RoundingMode::Downward);
return assetsTotal + magnitude;
}();
Number const roundedPosterior =
roundToAsset(asset, posterior, postScale, Number::RoundingMode::Downward);
actualDelta = STAmount{asset, roundedPosterior - assetsTotal};
}
XRPL_ASSERT(
abs(actualDelta) <= abs(delta),
"xrpl::clampToAssetsTotalScale : actual delta smaller or equal to calculated delta");
// Reject changes below scale precision (1 ULP) to prevent share balance changes
// without corresponding asset movements.
if (actualDelta <= beast::kZero)
return std::unexpected(tecPRECISION_LOSS);
return actualDelta;
}
[[nodiscard]] Number
assetsTotalForWithdrawal(SLE::ConstRef vault, WaiveUnrealizedLoss waive)
{
Number assetTotal = vault->at(sfAssetsTotal);
if (waive == WaiveUnrealizedLoss::No)
assetTotal -= vault->at(sfLossUnrealized);
return assetTotal;
}
[[nodiscard]] bool
debitIsNonZeroDust(Asset const& asset, Number const& total, Number const& amount)
{
@@ -149,7 +80,7 @@ debitIsNonZeroDust(Asset const& asset, Number const& total, Number const& amount
[[nodiscard]] std::optional<STAmount>
assetsToSharesWithdraw(
SLE::ConstRef vault,
VaultEntryR const& vault,
SLE::ConstRef issuance,
STAmount const& assets,
TruncateShares truncate,
@@ -162,7 +93,7 @@ assetsToSharesWithdraw(
if (assets.negative() || assets.asset() != vault->at(sfAsset))
return std::nullopt; // LCOV_EXCL_LINE
Number const assetTotal = assetsTotalForWithdrawal(vault, waive);
Number const assetTotal = vault.assetsTotalForWithdrawal(waive);
STAmount shares{vault->at(sfShareMPTID)};
if (assetTotal == 0)
return shares;
@@ -176,7 +107,7 @@ assetsToSharesWithdraw(
[[nodiscard]] std::optional<STAmount>
sharesToAssetsWithdraw(
SLE::ConstRef vault,
VaultEntryR const& vault,
SLE::ConstRef issuance,
STAmount const& shares,
WaiveUnrealizedLoss waive)
@@ -188,7 +119,7 @@ sharesToAssetsWithdraw(
if (shares.negative() || shares.asset() != vault->at(sfShareMPTID))
return std::nullopt; // LCOV_EXCL_LINE
Number const assetTotal = assetsTotalForWithdrawal(vault, waive);
Number const assetTotal = vault.assetsTotalForWithdrawal(waive);
STAmount assets{vault->at(sfAsset)};
if (assetTotal == 0)
return assets;
@@ -217,24 +148,6 @@ isSoleShareholder(ReadView const& view, AccountID const& account, SLE::ConstRef
return sleToken->getFieldU64(sfMPTAmount) == outstanding;
}
[[nodiscard]] VaultVersion
getVaultVersion(SLE::ConstRef vault)
{
XRPL_ASSERT(vault && vault->getType() == ltVAULT, "xrpl::getVaultVersion : valid Vault sle");
if (!vault->isFieldPresent(sfLEVersion))
return VaultVersion::Legacy;
auto const version = vault->at(sfLEVersion);
if (version > std::to_underlying(VaultVersion::CashBasis))
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::getVaultVersion : invalid vault version");
return VaultVersion::Legacy;
// LCOV_EXCL_STOP
}
return static_cast<VaultVersion>(version);
}
namespace {
[[nodiscard]] VaultKind
@@ -247,13 +160,6 @@ decodeVaultKind(std::optional<std::uint8_t> vaultKind)
} // namespace
[[nodiscard]] VaultKind
getVaultKind(SLE::ConstRef vault)
{
XRPL_ASSERT(vault && vault->getType() == ltVAULT, "xrpl::getVaultKind : valid Vault sle");
return decodeVaultKind(vault->at(~sfVaultKind));
}
[[nodiscard]] VaultKind
getVaultKind(STTx const& tx)
{
@@ -278,14 +184,6 @@ isValidClosedEndedGap(std::uint32_t sub, std::uint32_t red)
return r >= s + kMinInvestmentPeriod && r < s + kMaxInvestmentPeriod;
}
[[nodiscard]] VaultPhase
getVaultPhase(ReadView const& view, SLE::ConstRef vault)
{
XRPL_ASSERT(vault && vault->getType() == ltVAULT, "xrpl::getVaultPhase : valid Vault sle");
return getVaultPhase(
view, (*vault)[~sfVaultKind], (*vault)[~sfSubscriptionDate], (*vault)[~sfRedemptionDate]);
}
[[nodiscard]] VaultPhase
getVaultPhase(
ReadView const& view,
@@ -5,7 +5,7 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
@@ -160,7 +160,7 @@ ValidLoanBroker::finalize(
roundToAsset(
Asset{vault->at(sfAsset)},
debtTotal,
getAssetsTotalScale(vault),
VaultEntryR(vault, view).assetsTotalScale(),
Number::RoundingMode::TowardsZero) != beast::kZero)
{
JLOG(j.fatal())
+2 -1
View File
@@ -5,6 +5,7 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
@@ -75,7 +76,7 @@ ValidLoan::finalize(
auto const vault = view.read(keylet::vault(broker->at(sfVaultID)));
// We don't check for LendingProtocolV1_1 amendment because a ClosedEnded Vault will
// not exist without the amendment enabled
if (vault && getVaultKind(vault) == VaultKind::ClosedEnded)
if (vault && VaultEntryR(vault, view).kind() == VaultKind::ClosedEnded)
{
std::uint32_t const startDate = after->at(sfStartDate);
std::uint32_t const interval = after->at(sfPaymentInterval);
@@ -6,6 +6,7 @@
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
@@ -161,15 +162,15 @@ determineClawAmount(
SLE const& sleBroker,
Asset const& vaultAsset,
std::optional<STAmount> const& amount,
SLE::ConstRef vaultSle,
VaultEntryR const& vaultSle,
Rules const& rules)
{
auto const maxClawAmount = [&]() {
auto const minRequiredCover = [&]() {
if (rules.enabled(fixCleanup3_2_0))
{
return minimumBrokerCover(
sleBroker[sfDebtTotal], TenthBips32(sleBroker[sfCoverRateMinimum]), vaultSle);
return vaultSle.minimumBrokerCover(
sleBroker[sfDebtTotal], TenthBips32(sleBroker[sfCoverRateMinimum]));
}
// Always round the minimum required up
@@ -254,7 +255,7 @@ LoanBrokerCoverClawback::preclaim(PreclaimContext const& ctx)
auto const brokerPseudoAccountID = sleBroker->at(sfAccount);
auto const vault = ctx.view.read(keylet::vault(sleBroker->at(sfVaultID)));
VaultEntryR const vault(sleBroker->at(sfVaultID), ctx.view);
if (!vault)
{
// LCOV_EXCL_START
@@ -350,7 +351,7 @@ LoanBrokerCoverClawback::doApply()
auto const brokerPseudoID = *sleBroker->at(sfAccount);
auto const vault = view().read(keylet::vault(sleBroker->at(sfVaultID)));
VaultEntryR const vault(sleBroker->at(sfVaultID), view());
if (!vault)
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -3,6 +3,7 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/Number.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Feature.h>
@@ -62,7 +63,7 @@ LoanBrokerCoverDeposit::preclaim(PreclaimContext const& ctx)
JLOG(ctx.j.warn()) << "Account is not the owner of the LoanBroker.";
return tecNO_PERMISSION;
}
auto const vault = ctx.view.read(keylet::vault(sleBroker->at(sfVaultID)));
VaultEntryR const vault(sleBroker->at(sfVaultID), ctx.view);
if (!vault)
{
// LCOV_EXCL_START
@@ -143,7 +144,7 @@ LoanBrokerCoverDeposit::doApply()
if (!broker)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const vault = view().read(keylet::vault(broker->at(sfVaultID)));
VaultEntryR const vault(broker->at(sfVaultID), view());
if (!vault)
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -4,6 +4,7 @@
#include <xrpl/basics/Number.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
@@ -90,7 +91,7 @@ LoanBrokerCoverWithdraw::preclaim(PreclaimContext const& ctx)
JLOG(ctx.j.warn()) << "Account is not the owner of the LoanBroker.";
return tecNO_PERMISSION;
}
auto const vault = ctx.view.read(keylet::vault(sleBroker->at(sfVaultID)));
VaultEntryR const vault(sleBroker->at(sfVaultID), ctx.view);
if (!vault)
{
// LCOV_EXCL_START
@@ -172,8 +173,8 @@ LoanBrokerCoverWithdraw::preclaim(PreclaimContext const& ctx)
auto const minimumCover = [&]() {
if (fix320Enabled)
{
return minimumBrokerCover(
currentDebtTotal, TenthBips32{sleBroker->at(sfCoverRateMinimum)}, vault);
return vault.minimumBrokerCover(
currentDebtTotal, TenthBips32{sleBroker->at(sfCoverRateMinimum)});
}
// Always round the minimum required up.
@@ -218,7 +219,7 @@ LoanBrokerCoverWithdraw::doApply()
if (!broker)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const vault = view().read(keylet::vault(broker->at(sfVaultID)));
VaultEntryR const vault(broker->at(sfVaultID), view());
if (!vault)
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -3,6 +3,7 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/Number.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
@@ -62,7 +63,7 @@ LoanBrokerDelete::preclaim(PreclaimContext const& ctx)
return tecHAS_OBLIGATIONS;
}
auto const vault = ctx.view.read(keylet::vault(sleBroker->at(sfVaultID)));
VaultEntryR const vault(sleBroker->at(sfVaultID), ctx.view);
if (!vault)
{
// LCOV_EXCL_START
@@ -77,7 +78,7 @@ LoanBrokerDelete::preclaim(PreclaimContext const& ctx)
{
// Any remaining debt should have been wiped out by the last Loan
// Delete. This check is purely defensive.
auto const scale = getAssetsTotalScale(vault);
auto const scale = vault.assetsTotalScale();
auto const rounded =
roundToAsset(asset, debtTotal, scale, Number::RoundingMode::TowardsZero);
@@ -132,7 +133,7 @@ LoanBrokerDelete::doApply()
if (!broker)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const vaultID = broker->at(sfVaultID);
auto const sleVault = view().read(keylet::vault(vaultID));
VaultEntryR const sleVault(vaultID, view());
if (!sleVault)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const vaultPseudoID = sleVault->at(sfAccount);
@@ -5,6 +5,7 @@
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
@@ -98,7 +99,7 @@ LoanBrokerSet::preclaim(PreclaimContext const& ctx)
auto const account = tx[sfAccount];
auto const vaultID = tx[sfVaultID];
auto const sleVault = ctx.view.read(keylet::vault(vaultID));
VaultEntryR const sleVault(vaultID, ctx.view);
if (!sleVault)
{
JLOG(ctx.j.warn()) << "Vault does not exist.";
@@ -154,7 +155,7 @@ LoanBrokerSet::preclaim(PreclaimContext const& ctx)
// the constraint is enforced here, at the point where the vault
// is first bound to the lending protocol.
if (ctx.view.rules().enabled(featureLendingProtocolV1_1) &&
getVaultKind(sleVault) != VaultKind::ClosedEnded)
sleVault.kind() != VaultKind::ClosedEnded)
{
JLOG(ctx.j.warn()) << "LoanBroker requires a closed-ended Vault.";
return tecNO_PERMISSION;
@@ -204,7 +205,7 @@ LoanBrokerSet::doApply()
// LCOV_EXCL_STOP
}
auto const vault = view.read(keylet::vault(broker->at(sfVaultID)));
VaultEntryR const vault(broker->at(sfVaultID), view);
if (!vault)
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -223,7 +224,7 @@ LoanBrokerSet::doApply()
{
// Create a new LoanBroker pointing back to the given Vault
auto const vaultID = tx[sfVaultID];
auto const sleVault = view.read(keylet::vault(vaultID));
VaultEntryR const sleVault(vaultID, view);
if (!sleVault)
{
// This should be impossible
@@ -4,6 +4,7 @@
#include <xrpl/basics/Number.h> // IWYU pragma: keep
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/protocol/Indexes.h>
@@ -90,7 +91,7 @@ LoanDelete::doApply()
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const brokerPseudoAccount = brokerSle->at(sfAccount);
auto const vaultSle = view.peek(keylet::vault(brokerSle->at(sfVaultID)));
VaultEntryW vaultSle(brokerSle->at(sfVaultID), view);
if (!vaultSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const vaultAsset = vaultSle->at(sfAsset);
@@ -120,7 +121,7 @@ LoanDelete::doApply()
roundToAsset(
vaultSle->at(sfAsset),
debtTotalProxy,
getAssetsTotalScale(vaultSle),
vaultSle.assetsTotalScale(),
Number::RoundingMode::TowardsZero) == beast::kZero,
"xrpl::LoanDelete::doApply",
"last loan, remaining debt rounds to zero");
@@ -6,6 +6,7 @@
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Asset.h>
@@ -136,7 +137,7 @@ LoanManage::defaultLoan(
ApplyView& view,
SLE::Ref loanSle,
SLE::Ref brokerSle,
SLE::Ref vaultSle,
VaultEntryW& vaultSle,
Asset const& vaultAsset,
beast::Journal j)
{
@@ -176,7 +177,7 @@ LoanManage::defaultLoan(
// The vault may be at a different scale than the loan. Reduce rounding
// errors during the accounting by rounding some of the values to that
// scale.
auto const vaultScale = getAssetsTotalScale(vaultSle);
auto const vaultScale = vaultSle.assetsTotalScale();
{
// Decrease the Total Value of the Vault:
@@ -238,7 +239,7 @@ LoanManage::defaultLoan(
adjustImpreciseNumber(
vaultLossUnrealizedProxy, -totalDefaultAmount, vaultAsset, vaultScale);
}
view.update(vaultSle);
vaultSle.update();
}
// Update the LoanBroker object:
@@ -287,7 +288,7 @@ TER
LoanManage::impairLoan(
ApplyView& view,
SLE::Ref loanSle,
SLE::Ref vaultSle,
VaultEntryW& vaultSle,
Asset const& vaultAsset,
beast::Journal j)
{
@@ -304,7 +305,7 @@ LoanManage::impairLoan(
// The vault may be at a different scale than the loan. Reduce rounding
// errors during the accounting by rounding some of the values to that
// scale.
auto const vaultScale = getAssetsTotalScale(vaultSle);
auto const vaultScale = vaultSle.assetsTotalScale();
// Update the Vault object(set "paper loss")
auto vaultLossUnrealizedProxy = vaultSle->at(sfLossUnrealized);
@@ -316,7 +317,7 @@ LoanManage::impairLoan(
JLOG(j.warn()) << "Vault unrealized loss is too large, and will corrupt the vault.";
return tecLIMIT_EXCEEDED;
}
view.update(vaultSle);
vaultSle.update();
// Update the Loan object
loanSle->setFlag(lsfLoanImpaired);
@@ -339,14 +340,14 @@ LoanManage::impairLoan(
LoanManage::unimpairLoan(
ApplyView& view,
SLE::Ref loanSle,
SLE::Ref vaultSle,
VaultEntryW& vaultSle,
Asset const& vaultAsset,
beast::Journal j)
{
// The vault may be at a different scale than the loan. Reduce rounding
// errors during the accounting by rounding some of the values to that
// scale.
auto const vaultScale = getAssetsTotalScale(vaultSle);
auto const vaultScale = vaultSle.assetsTotalScale();
// Update the Vault object(clear "paper loss")
auto vaultLossUnrealizedProxy = vaultSle->at(sfLossUnrealized);
@@ -361,7 +362,7 @@ LoanManage::unimpairLoan(
// Reverse the "paper loss"
adjustImpreciseNumber(vaultLossUnrealizedProxy, -lossReversed, vaultAsset, vaultScale);
view.update(vaultSle);
vaultSle.update();
// Update the Loan object
loanSle->clearFlag(lsfLoanImpaired);
@@ -405,7 +406,7 @@ LoanManage::doApply()
if (!brokerSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const vaultSle = view.peek(keylet::vault(brokerSle->at(sfVaultID)));
VaultEntryW vaultSle(brokerSle->at(sfVaultID), view);
if (!vaultSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const vaultAsset = vaultSle->at(sfAsset);
@@ -7,6 +7,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/json/to_string.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
@@ -167,7 +168,7 @@ LoanPay::calculateBaseFee(ReadView const& view, STTx const& tx)
// Let preclaim worry about the error for this
return normalCost;
}
auto const vaultSle = view.read(keylet::vault(brokerSle->at(sfVaultID)));
VaultEntryR const vaultSle(brokerSle->at(sfVaultID), view);
if (!vaultSle)
{
// Let preclaim worry about the error for this
@@ -273,7 +274,7 @@ LoanPay::preclaim(PreclaimContext const& ctx)
// LCOV_EXCL_STOP
}
auto const vaultID = loanBrokerSle->at(sfVaultID);
auto const vaultSle = ctx.view.read(keylet::vault(vaultID));
VaultEntryR const vaultSle(vaultID, ctx.view);
if (!vaultSle)
{
// This should be impossible
@@ -349,7 +350,7 @@ LoanPay::doApply()
auto const brokerOwner = brokerSle->at(sfOwner);
auto const brokerPseudoAccount = brokerSle->at(sfAccount);
auto const vaultID = brokerSle->at(sfVaultID);
auto const vaultSle = view.peek(keylet::vault(vaultID));
VaultEntryW vaultSle(vaultID, view);
if (!vaultSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const vaultPseudoAccount = vaultSle->at(sfAccount);
@@ -360,7 +361,7 @@ LoanPay::doApply()
TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)};
auto debtTotalProxy = brokerSle->at(sfDebtTotal);
auto const vaultScale = getAssetsTotalScale(vaultSle);
auto const vaultScale = vaultSle.assetsTotalScale();
// Send the broker fee to the owner if they have sufficient cover available,
// _and_ if the owner can receive funds
@@ -377,7 +378,7 @@ LoanPay::doApply()
auto const minCover = [&]() {
if (view.rules().enabled(fixCleanup3_2_0))
{
return minimumBrokerCover(debtTotalProxy.value(), coverRateMinimum, vaultSle);
return vaultSle.minimumBrokerCover(debtTotalProxy.value(), coverRateMinimum);
}
// Round the minimum required cover up to be conservative. This ensures
// CoverAvailable never drops below the theoretical minimum, protecting
@@ -514,7 +515,7 @@ LoanPay::doApply()
//------------------------------------------------------
// Vault object state changes
view.update(vaultSle);
vaultSle.update();
Number const assetsAvailableBefore = *assetsAvailableProxy;
Number const assetsTotalBefore = *assetsTotalProxy;
@@ -7,6 +7,7 @@
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
@@ -313,7 +314,7 @@ LoanSet::preclaim(PreclaimContext const& ctx)
return terNO_ACCOUNT;
}
auto const vault = ctx.view.read(keylet::vault(brokerSle->at(sfVaultID)));
VaultEntryR const vault(brokerSle->at(sfVaultID), ctx.view);
if (!vault)
{
// Should be impossible
@@ -322,7 +323,7 @@ LoanSet::preclaim(PreclaimContext const& ctx)
if (ctx.view.rules().enabled(featureLendingProtocolV1_1))
{
auto const phase = getVaultPhase(ctx.view, vault);
auto const phase = vault.phase();
if (phase == VaultPhase::Subscription)
{
JLOG(ctx.j.warn()) << "Vault is still in the subscription phase.";
@@ -351,7 +352,7 @@ LoanSet::preclaim(PreclaimContext const& ctx)
// already at AssetsMaximum cannot take another loan. Cash-basis origination
// does not change AssetsTotal (see cash_basis::loanOriginationDeltas), so
// this leftover instant-recognition gate must not apply there.
if (getVaultVersion(vault) != VaultVersion::CashBasis && vault->at(sfAssetsMaximum) != 0 &&
if (vault.version() != VaultVersion::CashBasis && vault->at(sfAssetsMaximum) != 0 &&
vault->at(sfAssetsTotal) >= vault->at(sfAssetsMaximum))
{
JLOG(ctx.j.warn()) << "Vault at maximum assets limit. Can't add another loan.";
@@ -447,7 +448,7 @@ LoanSet::doApply()
if (!brokerOwnerSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const vaultSle = view.peek(keylet::vault(brokerSle->at(sfVaultID)));
VaultEntryW vaultSle(brokerSle->at(sfVaultID), view);
if (!vaultSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const vaultPseudo = vaultSle->at(sfAccount);
@@ -471,7 +472,7 @@ LoanSet::doApply()
auto vaultAvailableProxy = vaultSle->at(sfAssetsAvailable);
auto vaultTotalProxy = vaultSle->at(sfAssetsTotal);
auto const vaultScale = getAssetsTotalScale(vaultSle);
auto const vaultScale = vaultSle.assetsTotalScale();
if (vaultAvailableProxy < principalRequested)
{
JLOG(j_.warn()) << "Insufficient assets available in the Vault to fund the loan.";
@@ -499,8 +500,7 @@ LoanSet::doApply()
properties.loanState.managementFeeDue);
XRPL_ASSERT_PARTS(
*vaultSle->at(sfAssetsMaximum) == 0 ||
getVaultVersion(vaultSle) == VaultVersion::CashBasis ||
*vaultSle->at(sfAssetsMaximum) == 0 || vaultSle.version() == VaultVersion::CashBasis ||
*vaultSle->at(sfAssetsMaximum) > *vaultTotalProxy,
"xrpl::LoanSet::doApply",
"instant-recognition vault is below maximum limit");
@@ -565,7 +565,7 @@ LoanSet::doApply()
auto const minCover = [&]() {
if (ctx_.view().rules().enabled(fixCleanup3_2_0))
{
return minimumBrokerCover(newDebtTotal, coverRateMinimum, vaultSle);
return vaultSle.minimumBrokerCover(newDebtTotal, coverRateMinimum);
}
// Round the minimum required cover up to be conservative. This ensures
@@ -702,7 +702,7 @@ LoanSet::doApply()
*vaultAvailableProxy <= *vaultTotalProxy,
"xrpl::LoanSet::doApply",
"assets available must not be greater than assets outstanding");
view.update(vaultSle);
vaultSle.update();
// Update the balances in the loan broker
adjustImpreciseNumber(brokerSle->at(sfDebtTotal), debtTotalDelta, vaultAsset, vaultScale);
@@ -6,6 +6,7 @@
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
@@ -61,7 +62,7 @@ VaultClawback::preflight(PreflightContext const& ctx)
[[nodiscard]] STAmount
clawbackAmount(
SLE::ConstRef vault,
VaultEntryR const& vault,
std::optional<STAmount> const& maybeAmount,
AccountID const& account)
{
@@ -78,7 +79,7 @@ clawbackAmount(
TER
VaultClawback::preclaim(PreclaimContext const& ctx)
{
auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
VaultEntryR const vault(ctx.tx[sfVaultID], ctx.view);
if (!vault)
return tecNO_ENTRY;
@@ -232,7 +233,7 @@ VaultClawback::preclaim(PreclaimContext const& ctx)
std::expected<std::pair<STAmount, STAmount>, TER>
VaultClawback::assetsToClawback(
SLE::Ref vault,
VaultEntryW& vault,
SLE::ConstRef sleShareIssuance,
AccountID const& holder,
STAmount const& clawbackAmount)
@@ -362,7 +363,7 @@ VaultClawback::assetsToClawback(
// sub-ULP trimmed off stays in the vault for the remaining shareholders.
if (ctx_.view().rules().enabled(fixCleanup3_4_0) && assetsRecovered > beast::kZero)
{
auto const maybeClamped = clampToAssetsTotalScale(vault, -assetsRecovered);
auto const maybeClamped = vault.clampToAssetsTotalScale(-assetsRecovered);
if (!maybeClamped)
return std::unexpected(maybeClamped.error());
assetsRecovered = *maybeClamped;
@@ -390,7 +391,7 @@ TER
VaultClawback::doApply()
{
auto const& tx = ctx_.tx;
auto const vault = view().peek(keylet::vault(tx[sfVaultID]));
VaultEntryW vault(tx[sfVaultID], view());
if (!vault)
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -475,7 +476,7 @@ VaultClawback::doApply()
assetsTotal -= assetsRecovered;
assetsAvailable -= assetsRecovered;
view().update(vault);
vault.update();
auto const& vaultAccount = vault->at(sfAccount);
// Transfer shares from holder to vault.
@@ -5,6 +5,7 @@
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
@@ -185,9 +186,12 @@ VaultCreate::doApply()
if (owner == nullptr)
return tefINTERNAL; // LCOV_EXCL_LINE
auto vault = std::make_shared<SLE>(keylet::vault(accountID_, sequence));
VaultEntryW vault(accountID_, sequence, view());
vault.newSLE();
if (auto ter = dirLink(view(), accountID_, vault))
// dirLink takes a non-const SLE::pointer&, so pass it a copy of the handle.
SLE::pointer vaultSle = vault.mutableRawSle();
if (auto ter = dirLink(view(), accountID_, vaultSle))
return ter;
// We will create Vault and PseudoAccount, hence increase OwnerCount by 2
increaseOwnerCount(view(), owner, {}, 2, j_);
@@ -285,7 +289,7 @@ VaultCreate::doApply()
vault->at(sfRedemptionDate) = tx[sfRedemptionDate];
}
}
view().insert(vault);
vault.insert();
// Explicitly create MPToken for the vault owner
if (auto const err = authorizeMPToken(
@@ -3,6 +3,7 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
@@ -43,7 +44,7 @@ VaultDelete::preflight(PreflightContext const& ctx)
TER
VaultDelete::preclaim(PreclaimContext const& ctx)
{
auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
VaultEntryR const vault(ctx.tx[sfVaultID], ctx.view);
if (!vault)
return tecNO_ENTRY;
@@ -96,7 +97,7 @@ VaultDelete::preclaim(PreclaimContext const& ctx)
TER
VaultDelete::doApply()
{
auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID]));
VaultEntryW vault(ctx_.tx[sfVaultID], view());
auto applyViewContext = ctx_.getApplyViewContext();
if (!vault)
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -214,10 +215,10 @@ VaultDelete::doApply()
}
// We are destroying Vault and PseudoAccount, hence decrease by 2
decreaseOwnerCountForObject(view(), owner, vault, 2, j_);
decreaseOwnerCountForObject(view(), owner, vault.mutableRawSle(), 2, j_);
// Destroy the vault.
view().erase(vault);
vault.erase();
return tesSUCCESS;
}
@@ -6,6 +6,7 @@
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
@@ -33,7 +34,7 @@ namespace xrpl {
[[nodiscard]]
static STAmount
roundToVaultScale(STAmount const& amount, SLE::ConstRef vault)
roundToVaultScale(STAmount const& amount, VaultEntryR const& vault)
{
XRPL_ASSERT(vault && vault->getType() == ltVAULT, "xrpl::roundToVaultScale : valid vault sle");
XRPL_ASSERT(
@@ -103,13 +104,13 @@ VaultDeposit::preclaim(PreclaimContext const& ctx)
auto const fix320Enabled = ctx.view.rules().enabled(fixCleanup3_2_0);
auto const fix330Enabled = ctx.view.rules().enabled(fixCleanup3_3_0);
auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
VaultEntryR const vault(ctx.tx[sfVaultID], ctx.view);
if (!vault)
return tecNO_ENTRY;
if (ctx.view.rules().enabled(featureLendingProtocolV1_1))
{
auto const phase = getVaultPhase(ctx.view, vault);
auto const phase = vault.phase();
if (phase == VaultPhase::Investment || phase == VaultPhase::Redemption)
{
JLOG(ctx.j.debug()) << "VaultDeposit: vault deposit is not allowed in the investment "
@@ -231,7 +232,7 @@ VaultDeposit::doApply()
{
bool const fix320Enabled = view().rules().enabled(fixCleanup3_2_0);
bool const fix340Enabled = view().rules().enabled(fixCleanup3_4_0);
auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID]));
VaultEntryW vault(ctx_.tx[sfVaultID], view());
auto applyViewContext = ctx_.getApplyViewContext();
if (!vault)
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -350,7 +351,7 @@ VaultDeposit::doApply()
// than the depositor paid. Keep the share count from the first round trip: the clamp
// only drops a last digit of the new total. Converting the clamped amount back to
// shares would mint fewer shares while still charging the N-share debit.
auto const maybeClamped = clampToAssetsTotalScale(vault, assetsDeposited);
auto const maybeClamped = vault.clampToAssetsTotalScale(assetsDeposited);
if (!maybeClamped)
return maybeClamped.error();
assetsDeposited = *maybeClamped;
@@ -380,7 +381,7 @@ VaultDeposit::doApply()
vault->at(sfAssetsTotal) += assetsDeposited;
vault->at(sfAssetsAvailable) += assetsDeposited;
view().update(vault);
vault.update();
// A deposit must not push the vault over its limit.
auto const maximum = *vault->at(sfAssetsMaximum);
@@ -2,6 +2,7 @@
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
@@ -63,7 +64,7 @@ VaultSet::preflight(PreflightContext const& ctx)
TER
VaultSet::preclaim(PreclaimContext const& ctx)
{
auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
VaultEntryR const vault(ctx.tx[sfVaultID], ctx.view);
if (!vault)
return tecNO_ENTRY;
@@ -123,7 +124,7 @@ VaultSet::doApply()
auto const& tx = ctx_.tx;
// Update existing object.
auto vault = view().peek(keylet::vault(tx[sfVaultID]));
VaultEntryW vault(tx[sfVaultID], view());
if (!vault)
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -170,7 +171,7 @@ VaultSet::doApply()
// Note, we must update Vault object even if only DomainID is being updated
// in Issuance object. Otherwise it's really difficult for Vault invariants
// to verify the operation.
view().update(vault);
vault.update();
associateAsset(*vault, vaultAsset);
@@ -7,6 +7,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
@@ -83,13 +84,13 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx)
auto const fix330Enabled = ctx.view.rules().enabled(fixCleanup3_3_0);
auto const fix340Enabled = ctx.view.rules().enabled(fixCleanup3_4_0);
auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
VaultEntryR const vault(ctx.tx[sfVaultID], ctx.view);
if (!vault)
return tecNO_ENTRY;
if (ctx.view.rules().enabled(featureLendingProtocolV1_1))
{
if (getVaultPhase(ctx.view, vault) == VaultPhase::Investment)
if (vault.phase() == VaultPhase::Investment)
{
JLOG(ctx.j.debug())
<< "VaultWithdraw: vault withdrawal is not allowed in the investment phase.";
@@ -278,7 +279,7 @@ TER
VaultWithdraw::doApply()
{
bool const fix340Enabled = view().rules().enabled(fixCleanup3_4_0);
auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID]));
VaultEntryW vault(ctx_.tx[sfVaultID], view());
auto applyViewContext = ctx_.getApplyViewContext();
if (!vault)
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -394,7 +395,7 @@ VaultWithdraw::doApply()
// backing value. Reject rather than burn shares for a zero payout. The fixed-assets branch
// above has already rejected zero via the sharesRedeemed check.
if (amount.asset() == share && assetsWithdrawn == beast::kZero &&
assetsTotalForWithdrawal(vault, waiveUnrealizedLoss) != beast::kZero)
vault.assetsTotalForWithdrawal(waiveUnrealizedLoss) != beast::kZero)
{
JLOG(j_.debug()) << "VaultWithdraw: fixed-share withdrawal rounds to zero assets";
return tecPRECISION_LOSS;
@@ -469,7 +470,7 @@ VaultWithdraw::doApply()
// Round down at the posterior sfAssetsTotal scale so the payout never exceeds the
// value represented by the redeemed shares. sharesRedeemed is intentionally not
// re-derived: any trimmed residue stays with remaining shareholders.
auto const maybeClamped = clampToAssetsTotalScale(vault, -assetsWithdrawn);
auto const maybeClamped = vault.clampToAssetsTotalScale(-assetsWithdrawn);
if (!maybeClamped)
return maybeClamped.error(); // LCOV_EXCL_LINE
assetsWithdrawn = *maybeClamped;
@@ -543,7 +544,7 @@ VaultWithdraw::doApply()
assetsTotal -= assetsWithdrawn;
assetsAvailable -= assetsWithdrawn;
}
view().update(vault);
vault.update();
auto const& vaultAccount = vault->at(sfAccount);
+15 -10
View File
@@ -11,6 +11,7 @@
#include <xrpl/basics/Number.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -1682,7 +1683,8 @@ class LendingHelpers_test : public beast::unit_test::Suite
"loanOriginationDeltas dispatcher: amendment enabled, legacy vault picks "
"InstantRecognition");
Env const env{*this};
auto const deltas = loanOriginationDeltas(legacyVault, principalRequested, interestDue);
auto const deltas = loanOriginationDeltas(
VaultEntryR(legacyVault, *env.current()), principalRequested, interestDue);
auto const expected =
xrpl::instant_recognition::loanOriginationDeltas(principalRequested, interestDue);
BEAST_EXPECT(deltas.assetsTotalDelta == expected.assetsTotalDelta);
@@ -1694,8 +1696,8 @@ class LendingHelpers_test : public beast::unit_test::Suite
"loanOriginationDeltas dispatcher: amendment enabled, LEVersion == "
"VaultVersion::CashBasis picks CashBasis");
Env const env{*this};
auto const deltas =
loanOriginationDeltas(cashBasisVault, principalRequested, interestDue);
auto const deltas = loanOriginationDeltas(
VaultEntryR(cashBasisVault, *env.current()), principalRequested, interestDue);
auto const expected = xrpl::cash_basis::loanOriginationDeltas(principalRequested);
BEAST_EXPECT(deltas.assetsTotalDelta == expected.assetsTotalDelta);
BEAST_EXPECT(deltas.debtTotalDelta == expected.debtTotalDelta);
@@ -1721,7 +1723,8 @@ class LendingHelpers_test : public beast::unit_test::Suite
"picks InstantRecognition");
Env const env{*this};
BEAST_EXPECT(
loanOriginationExceedsVaultMaximum(legacyVault, vaultTotal, interestDue) ==
loanOriginationExceedsVaultMaximum(
VaultEntryR(legacyVault, *env.current()), vaultTotal, interestDue) ==
xrpl::instant_recognition::loanOriginationExceedsVaultMaximum(
vaultMaximum, vaultTotal, interestDue));
}
@@ -1732,8 +1735,8 @@ class LendingHelpers_test : public beast::unit_test::Suite
"VaultVersion::CashBasis picks CashBasis");
Env const env{*this};
BEAST_EXPECT(
loanOriginationExceedsVaultMaximum(cashBasisVault, vaultTotal, interestDue) ==
false);
loanOriginationExceedsVaultMaximum(
VaultEntryR(cashBasisVault, *env.current()), vaultTotal, interestDue) == false);
}
}
@@ -1752,7 +1755,7 @@ class LendingHelpers_test : public beast::unit_test::Suite
Env const env{*this};
auto sle = makeLoanSle(Number{1'000}, Number{800}, Number{50});
BEAST_EXPECT(
loanVaultExposure(legacyVault, sle) ==
loanVaultExposure(VaultEntryR(legacyVault, *env.current()), sle) ==
xrpl::instant_recognition::loanVaultExposure(sle));
}
@@ -1764,7 +1767,8 @@ class LendingHelpers_test : public beast::unit_test::Suite
Env const env{*this};
auto sle = makeLoanSle(Number{1'000}, Number{800}, Number{50});
BEAST_EXPECT(
loanVaultExposure(cashBasisVault, sle) == xrpl::cash_basis::loanVaultExposure(sle));
loanVaultExposure(VaultEntryR(cashBasisVault, *env.current()), sle) ==
xrpl::cash_basis::loanVaultExposure(sle));
}
}
@@ -1787,7 +1791,7 @@ class LendingHelpers_test : public beast::unit_test::Suite
"loanPaymentDeltas dispatcher: amendment enabled, legacy vault picks "
"InstantRecognition");
Env const env{*this};
auto const deltas = loanPaymentDeltas(legacyVault, parts);
auto const deltas = loanPaymentDeltas(VaultEntryR(legacyVault, *env.current()), parts);
auto const expected = xrpl::instant_recognition::loanPaymentDeltas(parts);
BEAST_EXPECT(deltas.assetsTotalDelta == expected.assetsTotalDelta);
BEAST_EXPECT(deltas.debtTotalDelta == expected.debtTotalDelta);
@@ -1799,7 +1803,8 @@ class LendingHelpers_test : public beast::unit_test::Suite
"VaultVersion::CashBasis "
"picks CashBasis");
Env const env{*this};
auto const deltas = loanPaymentDeltas(cashBasisVault, parts);
auto const deltas =
loanPaymentDeltas(VaultEntryR(cashBasisVault, *env.current()), parts);
auto const expected = xrpl::cash_basis::loanPaymentDeltas(parts);
BEAST_EXPECT(deltas.assetsTotalDelta == expected.assetsTotalDelta);
BEAST_EXPECT(deltas.debtTotalDelta == expected.debtTotalDelta);
+2 -2
View File
@@ -13,8 +13,8 @@
#include <xrpl/basics/chrono.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
@@ -1126,7 +1126,7 @@ private:
auto const vaultSle = env.le(broker.vaultKeylet());
BEAST_EXPECT(vaultSle);
BEAST_EXPECT(!vaultSle->isFieldPresent(sfLEVersion));
BEAST_EXPECT(getVaultVersion(vaultSle) == VaultVersion::Legacy);
BEAST_EXPECT(VaultEntryR(vaultSle, *env.current()).version() == VaultVersion::Legacy);
}
}
+7 -5
View File
@@ -14,6 +14,7 @@
#include <xrpl/basics/Number.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/json/json_value.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
@@ -665,7 +666,7 @@ private:
return std::nullopt;
if (!BEAST_EXPECT(tinyLoanSle->at(sfLoanScale) == -12) ||
!BEAST_EXPECT(bigLoanSle->at(sfLoanScale) == -11) ||
!BEAST_EXPECT(getAssetsTotalScale(vaultSle) == -11))
!BEAST_EXPECT(VaultEntryR(vaultSle, *env.current()).assetsTotalScale() == -11))
return std::nullopt;
// Use issuer clawback to reduce cover to the minimum the
@@ -792,7 +793,7 @@ private:
auto const coverAvail = brokerSle->at(sfCoverAvailable);
auto const debtTotal = brokerSle->at(sfDebtTotal);
auto const vaultScale = getAssetsTotalScale(vaultSle);
auto const vaultScale = VaultEntryR(vaultSle, *env.current()).assetsTotalScale();
auto const debtScale = scale(debtTotal, asset);
// Sanity: debt scale differs from vault scale for this setup.
@@ -805,8 +806,9 @@ private:
tenthBipsOfValue(debtTotal, TenthBips32{c.brokerParams.coverRateMin}),
debtScale);
}();
auto const newMin = minimumBrokerCover(
debtTotal, TenthBips32{c.brokerParams.coverRateMin}, vaultSle);
auto const newMin =
VaultEntryR(vaultSle, *env.current())
.minimumBrokerCover(debtTotal, TenthBips32{c.brokerParams.coverRateMin});
// The new (vaultScale) minimum must be strictly larger than
// the old (debtScale) minimum — that is the gap the amendment
@@ -890,7 +892,7 @@ private:
auto const vaultSle = env.le(keylet::vault(c.broker.vaultID));
if (!BEAST_EXPECT(vaultSle))
return;
auto const vaultScale = getAssetsTotalScale(vaultSle);
auto const vaultScale = VaultEntryR(vaultSle, *env.current()).assetsTotalScale();
BEAST_EXPECT(vaultScale == -11);
// Now try to create a tiny additional loan. Principal is
+8 -5
View File
@@ -26,9 +26,9 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/json/json_value.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -190,7 +190,7 @@ protected:
using namespace jtx;
auto const vaultSle = env.le(keylet::vault(vaultID));
return getAssetsTotalScale(vaultSle);
return VaultEntryR(vaultSle, *env.current()).assetsTotalScale();
}
};
@@ -346,7 +346,8 @@ protected:
{
auto const expectedDebt =
env.current()->rules().enabled(featureLendingProtocolV1_1) &&
getVaultVersion(vaultSle) == VaultVersion::CashBasis
VaultEntryR(vaultSle, *env.current()).version() ==
VaultVersion::CashBasis
? principalOutstanding
: principalOutstanding + interestOwed;
env.test.BEAST_EXPECT(brokerDebt == expectedDebt);
@@ -451,7 +452,8 @@ protected:
env.test.BEAST_EXPECT(
vaultSle->at(sfLossUnrealized) ==
(env.current()->rules().enabled(featureLendingProtocolV1_1) &&
getVaultVersion(vaultSle) == VaultVersion::CashBasis
VaultEntryR(vaultSle, *env.current()).version() ==
VaultVersion::CashBasis
? principalOutstanding
: totalValue - managementFeeOutstanding));
}
@@ -666,7 +668,8 @@ protected:
vaultSle->at(sfAssetsTotal) - vaultSle->at(sfAssetsAvailable);
auto const unrealizedLoss = vaultSle->at(sfLossUnrealized) +
(env.current()->rules().enabled(featureLendingProtocolV1_1) &&
getVaultVersion(vaultSle) == VaultVersion::CashBasis
VaultEntryR(vaultSle, *env.current()).version() ==
VaultVersion::CashBasis
? state.principalOutstanding
: state.totalValue - state.managementFeeOutstanding);
+2 -1
View File
@@ -22,6 +22,7 @@
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/json/json_forwards.h>
#include <xrpl/json/json_value.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
@@ -1521,7 +1522,7 @@ private:
// same conversion helper VaultClawback itself uses, rather than
// assuming an exact 90/10 split holds under truncation.
auto const maybeSharesDestroyed = assetsToSharesWithdraw(
vaultBefore,
VaultEntryR(vaultBefore, *env.current()),
issuanceBefore,
setup.usd(9'000).value(),
TruncateShares::Yes,
+2 -2
View File
@@ -15,7 +15,7 @@
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/json/json_forwards.h>
#include <xrpl/json/json_value.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -473,7 +473,7 @@ private:
auto const sle = env.le(keylet);
if (!BEAST_EXPECT(sle))
return;
BEAST_EXPECT(getVaultPhase(*env.current(), sle) == VaultPhase::NoPhase);
BEAST_EXPECT(VaultEntryR(sle, *env.current()).phase() == VaultPhase::NoPhase);
};
// Advance the clock through a wide range of ledger times: an open-ended vault's phase
+9 -4
View File
@@ -1,9 +1,10 @@
#include <test/jtx/Account.h>
#include <test/jtx/Env.h>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
#include <xrpl/ledger/entries/VaultEntry.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
@@ -29,8 +30,9 @@ namespace xrpl {
// True unit test of `clampToAssetsTotalScale`. The function under test only
// reads sfAsset and sfAssetsTotal from the vault SLE and never touches a
// ledger view or Rules, so a bare in-memory ltVAULT SLE is enough; there is
// no jtx::Env and no transaction submitted anywhere in this file.
// ledger view or Rules, so a bare in-memory ltVAULT SLE is enough. The only
// jtx::Env is the one runCases() needs so VaultEntryR can wrap that SLE; no
// transaction is submitted anywhere in this file.
//
// Number regime: this suite relies on the default thread_local Number
// mantissa range, which src/libxrpl/basics/Number.cpp initializes to
@@ -86,6 +88,9 @@ private:
Number::RoundingMode::Upward,
Number::RoundingMode::TowardsZero};
test::jtx::Env const env{*this};
auto const view = env.current();
for (auto const& c : cases)
{
testcase(c.name);
@@ -106,7 +111,7 @@ private:
for (auto const mode : modes)
{
NumberRoundModeGuard const rg(mode);
auto const result = clampToAssetsTotalScale(vault, delta);
auto const result = VaultEntryR(vault, *view).clampToAssetsTotalScale(delta);
// The function must be insensitive to the caller's ambient
// rounding mode: every mode must agree with the first one
+14 -3
View File
@@ -38,6 +38,7 @@
#include <xrpl/ledger/entries/XChainOwnedClaimIDEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/XChainOwnedCreateAccountClaimIDEntry.h> // IWYU pragma: keep
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
@@ -76,12 +77,22 @@ template class SLEBase<ApplyView>;
#pragma push_macro("LEDGER_ENTRY")
#undef LEDGER_ENTRY
// No explicit instantiation here, neither a definition nor a declaration.
// A definition would instantiate an entry a second time once its member
// bodies live in its own .cpp (MSVC rejects that with C4661 under /WX), and a
// declaration (`extern template`) stops clang from emitting the inline
// constructors of the header-only entries at -O0, which leaves them undefined
// at link time in the debug builds. Naming the class in a static_assert is
// enough to instantiate its definition, which is all this exhaustiveness
// check needs; the per-type suites exercise the members.
#define LEDGER_ENTRY(tag, value, name, ...) \
template class name##Entry<ReadView>; \
template class name##Entry<ApplyView>; \
static_assert( \
name##Entry<ReadView>::kEntryType == tag && name##Entry<ApplyView>::kEntryType == tag, \
#name "Entry must be bound to " #tag);
#name "Entry must be bound to " #tag); \
static_assert( \
std::is_constructible_v<name##Entry<ReadView>, Keylet const&, ReadView const&> && \
std::is_constructible_v<name##Entry<ApplyView>, Keylet const&, ApplyView&>, \
#name "Entry must be constructible from a Keylet and a view");
#include <xrpl/protocol/detail/ledger_entries.macro>