Compare commits

...

5 Commits

Author SHA1 Message Date
Mayukha Vadari
72dda48103 Include Keylet.h in the SLEBase test
(cherry picked from commit 3cca9d3266)
2026-10-05 14:39:20 +01:00
Mayukha Vadari
7e25d8cd5a 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:39:20 +01:00
Mayukha Vadari
4f314a2e67 refactor: Move constructLoanState onto LoanEntry
Replace the LoanEntryR overload of constructLoanState() with a const
member LoanEntry::state(). The raw-fields overload stays free and the
member calls it. The body lives in the new
src/libxrpl/ledger/entries/LoanEntry.cpp with explicit instantiation.
LoanState is forward-declared in LoanEntry.h because LendingHelpers.h,
which defines it, includes LoanEntry.h.
2026-10-05 13:46:19 +01:00
Mayukha Vadari
2d427ec676 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:46:19 +01:00
Mayukha Vadari
1ff6147795 refactor: Use LoanEntry everywhere
Replace raw Loan SLE pointers with LoanEntryR/LoanEntryW in the lending
transactors and LendingHelpers. Helpers that take a Loan SLE
(constructLoanState, isPaymentLate, the loanVaultExposure family,
loanMakePayment and its internal payment helpers, and
LoanManage::defaultLoan/impairLoan/unimpairLoan) now take
LoanEntryR const& or LoanEntryW& and stay free functions.

The compiler now enforces read-only vs. writable access to Loan
entries. Vault, LoanBroker and other entry types are unchanged. Beast
tests wrap their raw SLEs where a signature change requires it.

Pure refactor: no behavior change.
2026-10-05 13:32:45 +01:00
13 changed files with 127 additions and 75 deletions

View File

@@ -11,6 +11,10 @@
namespace xrpl {
// Defined in <xrpl/ledger/helpers/LendingHelpers.h>, which includes this
// header; include that header to call state().
struct LoanState;
template <typename ViewT>
class LoanEntry : public SLEBase<ViewT, ltLOAN>
{
@@ -37,6 +41,11 @@ public:
: Base(keylet::loan(loanID), view, j)
{
}
// Build a LoanState from the three tracked fields. The Loan ledger object
// always holds rounded values.
[[nodiscard]] LoanState
state() const;
};
using LoanEntryR = LoanEntry<ReadView>;

View File

@@ -7,6 +7,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/LoanEntry.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/LedgerFormats.h> // IWYU pragma: keep
@@ -301,12 +302,6 @@ constructLoanState(
Number const& principalOutstanding,
Number const& managementFeeOutstanding);
// Overload of constructLoanState() that reads the three tracked fields
// directly from a Loan ledger object, which always holds rounded values,
// rather than taking them as separate Number arguments.
LoanState
constructLoanState(SLE::ConstRef loan);
Number
computeManagementFee(
Asset const& asset,
@@ -328,7 +323,7 @@ computeFullPaymentInterest(
// boundary is amendment-gated: with fixCleanup3_4_0 the due date must be
// strictly in the past, otherwise the exact due-date instant counts as late.
[[nodiscard]] bool
isPaymentLate(ReadView const& view, SLE::ConstRef loanSle);
isPaymentLate(ReadView const& view, LoanEntryR const& loanSle);
// Deltas applied to Vault.AssetsTotal and LoanBroker.DebtTotal at a single
// accounting touch point (origination, payment, impair/unimpair/default).
@@ -356,7 +351,7 @@ loanOriginationExceedsVaultMaximum(
// LoanManage impair/unimpair/default: the vault's exposure to this loan
Number
loanVaultExposure(SLE::ConstRef loanSle);
loanVaultExposure(LoanEntryR const& loanSle);
// LoanPay: what's added to Vault.AssetsTotal and subtracted from LoanBroker.DebtTotal for a payment
AccountingDeltas
@@ -372,7 +367,7 @@ AccountingDeltas
loanOriginationDeltas(Number const& principalRequested);
Number
loanVaultExposure(SLE::ConstRef loanSle);
loanVaultExposure(LoanEntryR const& loanSle);
AccountingDeltas
loanPaymentDeltas(LoanPaymentParts const& parts);
@@ -396,7 +391,7 @@ loanOriginationExceedsVaultMaximum(
Number const& interestDue);
Number
loanVaultExposure(SLE::ConstRef vaultSle, SLE::ConstRef loanSle);
loanVaultExposure(SLE::ConstRef vaultSle, LoanEntryR const& loanSle);
AccountingDeltas
loanPaymentDeltas(SLE::ConstRef vaultSle, LoanPaymentParts const& parts);
@@ -667,7 +662,7 @@ std::expected<LoanPaymentParts, TER>
loanMakePayment(
Asset const& asset,
ApplyView& view,
SLE::Ref loan,
LoanEntryW& loan,
SLE::ConstRef brokerSle,
STAmount const& amount,
LoanPaymentType const paymentType,

View File

@@ -4,6 +4,7 @@
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/LoanEntry.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
@@ -42,7 +43,7 @@ public:
static TER
defaultLoan(
ApplyView& view,
SLE::Ref loanSle,
LoanEntryW& loanSle,
SLE::Ref brokerSle,
SLE::Ref vaultSle,
Asset const& vaultAsset,
@@ -54,7 +55,7 @@ public:
static TER
impairLoan(
ApplyView& view,
SLE::Ref loanSle,
LoanEntryW& loanSle,
SLE::Ref vaultSle,
Asset const& vaultAsset,
beast::Journal j);
@@ -65,7 +66,7 @@ public:
[[nodiscard]] static TER
unimpairLoan(
ApplyView& view,
SLE::Ref loanSle,
LoanEntryW& loanSle,
SLE::Ref vaultSle,
Asset const& vaultAsset,
beast::Journal j);

View File

@@ -0,0 +1,29 @@
#include <xrpl/ledger/entries/LoanEntry.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
namespace xrpl {
template <typename ViewT>
LoanState
LoanEntry<ViewT>::state() const
{
XRPL_ASSERT(
this->exists() && (*this)->getType() == ltLOAN,
"xrpl::constructLoanState : valid loan SLE");
return constructLoanState(
(*this)->at(sfTotalValueOutstanding),
(*this)->at(sfPrincipalOutstanding),
(*this)->at(sfManagementFeeOutstanding));
}
template class LoanEntry<ReadView>;
template class LoanEntry<ApplyView>;
} // namespace xrpl

View File

@@ -9,6 +9,7 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/LoanEntry.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
@@ -91,7 +92,7 @@ getLoanDefaultFreezeExemptAccounts(ReadView const& view, STTx const& tx)
// Unlike the broker/vault lookups below, the submitter picks the LoanID,
// so a nonexistent Loan is an ordinary (if unusual) input, not a
// structural impossibility -- exercised directly in LendingHelpers_test.
auto const loanSle = view.read(keylet::loan(tx[sfLoanID]));
LoanEntryR const loanSle(tx[sfLoanID], view);
if (!loanSle)
return std::nullopt;
@@ -170,7 +171,7 @@ isRounded(Asset const& asset, Number const& value, std::int32_t scale)
}
[[nodiscard]] bool
isPaymentLate(ReadView const& view, SLE::ConstRef loanSle)
isPaymentLate(ReadView const& view, LoanEntryR const& loanSle)
{
return hasExpired(
view,
@@ -204,7 +205,7 @@ DefaultAmount = (Loan.PrincipalOutstanding + Loan.InterestOutstanding)
Which is equivalent to (Loan.TotalValueOutstanding - Loan.ManagementFeeOutstanding)
*/
Number
loanVaultExposure(SLE::ConstRef loanSle)
loanVaultExposure(LoanEntryR const& loanSle)
{
return loanSle->at(sfTotalValueOutstanding) - loanSle->at(sfManagementFeeOutstanding);
}
@@ -233,7 +234,7 @@ loanOriginationDeltas(Number const& principalRequested)
* DefaultAmount = Loan.PrincipalOutstanding
*/
Number
loanVaultExposure(SLE::ConstRef loanSle)
loanVaultExposure(LoanEntryR const& loanSle)
{
return loanSle->at(sfPrincipalOutstanding);
}
@@ -288,7 +289,7 @@ loanOriginationExceedsVaultMaximum(
}
Number
loanVaultExposure(SLE::ConstRef vaultSle, SLE::ConstRef loanSle)
loanVaultExposure(SLE::ConstRef vaultSle, LoanEntryR const& loanSle)
{
return cashBasisEnabled(vaultSle) ? cash_basis::loanVaultExposure(loanSle)
: instant_recognition::loanVaultExposure(loanSle);
@@ -579,7 +580,7 @@ loanAccruedInterest(
* a computed payment.
*/
LoanPaymentParts
doPayment(ExtendedPaymentComponents const& payment, SLE::Ref loan)
doPayment(ExtendedPaymentComponents const& payment, LoanEntryW& loan)
{
auto totalValueOutstandingProxy = loan->at(sfTotalValueOutstanding);
auto principalOutstandingProxy = loan->at(sfPrincipalOutstanding);
@@ -905,7 +906,7 @@ doOverpayment(
Asset const& asset,
std::int32_t loanScale,
ExtendedPaymentComponents const& overpaymentComponents,
SLE::Ref loan,
LoanEntryW& loan,
Number const& periodicRate,
TenthBips16 const managementFeeRate,
beast::Journal j)
@@ -1047,7 +1048,7 @@ std::expected<ExtendedPaymentComponents, TER>
computeLatePayment(
Asset const& asset,
ReadView const& view,
SLE::ConstRef loan,
LoanEntryR const& loan,
ExtendedPaymentComponents const& periodic,
STAmount const& amount,
TenthBips16 managementFeeRate,
@@ -1143,7 +1144,7 @@ std::expected<ExtendedPaymentComponents, TER>
computeFullPayment(
Asset const& asset,
ReadView const& view,
SLE::ConstRef loan,
LoanEntryR const& loan,
Number const& periodicRate,
STAmount const& amount,
TenthBips16 managementFeeRate,
@@ -1182,7 +1183,7 @@ computeFullPayment(
auto const [roundedFullInterest, roundedFullManagementFee] = roundAndSplitInterest(
asset, fullPaymentInterest, managementFeeRate, loanScale, Number::RoundingMode::Downward);
LoanState const loanState = constructLoanState(loan);
LoanState const loanState = loan.state();
Number const principalOutstanding = loanState.principalOutstanding;
Number const managementFeeOutstanding = loanState.managementFeeDue;
Number const totalInterestOutstanding = loanState.interestDue;
@@ -1489,7 +1490,7 @@ PaymentComponents
computePaymentComponents(
Rules const& rules,
Asset const& asset,
SLE::Ref loan,
LoanEntryW& loan,
Number const& periodicRate,
TenthBips16 managementFeeRate)
{
@@ -1587,7 +1588,7 @@ computeOverpaymentComponents(
* interest rate.
*/
std::pair<TenthBips16, Number>
loanRatesFor(SLE::ConstRef loan, SLE::ConstRef brokerSle)
loanRatesFor(LoanEntryR const& loan, SLE::ConstRef brokerSle)
{
TenthBips16 const managementFeeRate{brokerSle->at(sfManagementFeeRate)};
TenthBips32 const interestRate{loan->at(sfInterestRate)};
@@ -1604,7 +1605,7 @@ std::expected<LoanPaymentParts, TER>
makeFullPayment(
Asset const& asset,
ApplyView& view,
SLE::Ref loan,
LoanEntryW& loan,
SLE::ConstRef brokerSle,
STAmount const& amount,
beast::Journal j)
@@ -1629,7 +1630,7 @@ std::expected<LoanPaymentParts, TER>
makeLatePayment(
Asset const& asset,
ApplyView const& view,
SLE::Ref loan,
LoanEntryW& loan,
SLE::ConstRef brokerSle,
STAmount const& amount,
beast::Journal j)
@@ -1663,7 +1664,7 @@ std::expected<LoanPaymentParts, TER>
makeRegularPayment(
Asset const& asset,
ApplyView const& view,
SLE::Ref loan,
LoanEntryW& loan,
SLE::ConstRef brokerSle,
STAmount const& amount,
LoanPaymentType const paymentType,
@@ -2099,17 +2100,6 @@ constructLoanState(
.managementFeeDue = managementFeeOutstanding};
}
LoanState
constructLoanState(SLE::ConstRef loan)
{
XRPL_ASSERT(loan && loan->getType() == ltLOAN, "xrpl::constructLoanState : valid loan SLE");
return constructLoanState(
loan->at(sfTotalValueOutstanding),
loan->at(sfPrincipalOutstanding),
loan->at(sfManagementFeeOutstanding));
}
/*
* This function calculates the fee owed to the broker based on the asset,
* value, and management fee rate.
@@ -2254,7 +2244,7 @@ std::expected<LoanPaymentParts, TER>
loanMakePayment(
Asset const& asset,
ApplyView& view,
SLE::Ref loan,
LoanEntryW& loan,
SLE::ConstRef brokerSle,
STAmount const& amount,
LoanPaymentType const paymentType,
@@ -2280,7 +2270,7 @@ loanMakePayment(
XRPL_ASSERT(
*loan->at(sfTotalValueOutstanding) > 0, "xrpl::loanMakePayment : valid total value");
view.update(loan);
loan.update();
// -------------------------------------------------------------
// A late payment not flagged as late overrides all other options.

View File

@@ -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/LoanEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/protocol/Indexes.h>
@@ -41,7 +42,7 @@ LoanDelete::preclaim(PreclaimContext const& ctx)
auto const account = tx[sfAccount];
auto const loanID = tx[sfLoanID];
auto const loanSle = ctx.view.read(keylet::loan(loanID));
LoanEntryR const loanSle(loanID, ctx.view);
if (!loanSle)
{
JLOG(ctx.j.warn()) << "Loan does not exist.";
@@ -76,7 +77,7 @@ LoanDelete::doApply()
auto& view = ctx_.view();
auto const loanID = tx[sfLoanID];
auto const loanSle = view.peek(keylet::loan(loanID));
LoanEntryW loanSle(loanID, view);
if (!loanSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
auto const borrower = loanSle->at(sfBorrower);
@@ -104,7 +105,9 @@ LoanDelete::doApply()
return tefBAD_LEDGER; // LCOV_EXCL_LINE
// Delete the Loan object
view.erase(loanSle);
// Erase through the view, not loanSle.erase(): decreaseOwnerCountForObject
// below still reads the erased SLE.
view.erase(loanSle.mutableRawSle());
// Decrement the LoanBroker's owner count.
adjustLoanBrokerOwnerCount(view, brokerSle, -1, j_);
@@ -128,7 +131,7 @@ LoanDelete::doApply()
}
}
// Decrement the borrower's owner count
decreaseOwnerCountForObject(view, borrowerSle, loanSle, 1, j_);
decreaseOwnerCountForObject(view, borrowerSle, loanSle.mutableRawSle(), 1, j_);
associateAsset(*vaultSle, vaultAsset);

View File

@@ -6,6 +6,7 @@
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/LoanEntry.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Asset.h>
@@ -69,7 +70,7 @@ LoanManage::preclaim(PreclaimContext const& ctx)
auto const account = tx[sfAccount];
auto const loanID = tx[sfLoanID];
auto const loanSle = ctx.view.read(keylet::loan(loanID));
LoanEntryR const loanSle(loanID, ctx.view);
if (!loanSle)
{
JLOG(ctx.j.warn()) << "Loan does not exist.";
@@ -134,7 +135,7 @@ LoanManage::preclaim(PreclaimContext const& ctx)
TER
LoanManage::defaultLoan(
ApplyView& view,
SLE::Ref loanSle,
LoanEntryW& loanSle,
SLE::Ref brokerSle,
SLE::Ref vaultSle,
Asset const& vaultAsset,
@@ -269,7 +270,7 @@ LoanManage::defaultLoan(
// Zero out the next due date. Since it's default, it'll be removed from
// the object.
loanSle->at(sfNextPaymentDueDate) = 0;
view.update(loanSle);
loanSle.update();
// Return funds from the LoanBroker pseudo-account to the
// Vault pseudo-account:
@@ -286,7 +287,7 @@ LoanManage::defaultLoan(
TER
LoanManage::impairLoan(
ApplyView& view,
SLE::Ref loanSle,
LoanEntryW& loanSle,
SLE::Ref vaultSle,
Asset const& vaultAsset,
beast::Journal j)
@@ -330,7 +331,7 @@ LoanManage::impairLoan(
loanNextDueProxy = view.parentCloseTime().time_since_epoch().count();
}
}
view.update(loanSle);
loanSle.update();
return tesSUCCESS;
}
@@ -338,7 +339,7 @@ LoanManage::impairLoan(
[[nodiscard]] TER
LoanManage::unimpairLoan(
ApplyView& view,
SLE::Ref loanSle,
LoanEntryW& loanSle,
SLE::Ref vaultSle,
Asset const& vaultAsset,
beast::Journal j)
@@ -384,7 +385,7 @@ LoanManage::unimpairLoan(
view.parentCloseTime().time_since_epoch().count() + paymentInterval;
}
}
view.update(loanSle);
loanSle.update();
return tesSUCCESS;
}
@@ -396,7 +397,7 @@ LoanManage::doApply()
auto& view = ctx_.view();
auto const loanID = tx[sfLoanID];
auto const loanSle = view.peek(keylet::loan(loanID));
LoanEntryW loanSle(loanID, view);
if (!loanSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE

View File

@@ -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/LoanEntry.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
@@ -137,7 +138,7 @@ LoanPay::calculateBaseFee(ReadView const& view, STTx const& tx)
auto const amount = tx[sfAmount];
auto const loanID = tx[sfLoanID];
auto const loanSle = view.read(keylet::loan(loanID));
LoanEntryR const loanSle(loanID, view);
if (!loanSle)
{
// Let preclaim worry about the error for this
@@ -234,7 +235,7 @@ LoanPay::preclaim(PreclaimContext const& ctx)
auto const loanID = tx[sfLoanID];
auto const amount = tx[sfAmount];
auto const loanSle = ctx.view.read(keylet::loan(loanID));
LoanEntryR const loanSle(loanID, ctx.view);
if (!loanSle)
{
JLOG(ctx.j.warn()) << "Loan does not exist.";
@@ -337,7 +338,7 @@ LoanPay::doApply()
auto const amount = tx[sfAmount];
auto const loanID = tx[sfLoanID];
auto const loanSle = view.peek(keylet::loan(loanID));
LoanEntryW loanSle(loanID, view);
if (!loanSle)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
std::int32_t const loanScale = loanSle->at(sfLoanScale);
@@ -442,7 +443,7 @@ LoanPay::doApply()
// If the payment computation completed without error, the loanSle object
// has been modified.
view.update(loanSle);
loanSle.update();
XRPL_ASSERT_PARTS(
// It is possible to pay 0 principal

View File

@@ -7,6 +7,7 @@
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/LoanEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
@@ -655,8 +656,8 @@ LoanSet::doApply()
auto loanSequenceProxy = brokerSle->at(sfLoanSequence);
// Create the loan
auto loan =
std::make_shared<SLE>(keylet::loan(brokerID, SeqProxy::rawSequence(*loanSequenceProxy)));
LoanEntryW loan(brokerID, SeqProxy::rawSequence(*loanSequenceProxy), view);
loan.newSLE();
// Prevent copy/paste errors
auto setLoanField = [&loan, &tx](auto const& field, std::uint32_t const defValue = 0) {
@@ -693,7 +694,7 @@ LoanSet::doApply()
loan->at(sfPreviousPaymentDueDate) = 0;
loan->at(sfNextPaymentDueDate) = startDate + paymentInterval;
loan->at(sfPaymentRemaining) = paymentTotal;
view.insert(loan);
loan.insert();
// Update the balances in the vault
vaultAvailableProxy -= principalRequested;
@@ -714,11 +715,13 @@ LoanSet::doApply()
return tecMAX_SEQUENCE_REACHED;
view.update(brokerSle);
// dirLink takes a non-const SLE::pointer&, so pass it a copy of the handle.
SLE::pointer loanSle = loan.mutableRawSle();
// Put the loan into the pseudo-account's directory
if (auto const ter = dirLink(view, brokerPseudo, loan, sfLoanBrokerNode))
if (auto const ter = dirLink(view, brokerPseudo, loanSle, sfLoanBrokerNode))
return ter;
// Borrower is the owner of the loan
if (auto const ter = dirLink(view, borrower, loan, sfOwnerNode))
if (auto const ter = dirLink(view, borrower, loanSle, sfOwnerNode))
return ter;
associateAsset(*vaultSle, vaultAsset);

View File

@@ -11,6 +11,7 @@
#include <xrpl/basics/Number.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/ledger/entries/LoanEntry.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -1624,8 +1625,11 @@ class LendingHelpers_test : public beast::unit_test::Suite
{
testcase("instant_recognition::loanVaultExposure");
jtx::Env const env{*this};
auto sle = makeLoanSle(Number{1'000}, Number{800}, Number{50});
BEAST_EXPECT(xrpl::instant_recognition::loanVaultExposure(sle) == Number{950});
BEAST_EXPECT(
xrpl::instant_recognition::loanVaultExposure(LoanEntryR(sle, *env.current())) ==
Number{950});
}
void
@@ -1633,8 +1637,10 @@ class LendingHelpers_test : public beast::unit_test::Suite
{
testcase("cash_basis::loanVaultExposure");
jtx::Env const env{*this};
auto sle = makeLoanSle(Number{1'000}, Number{800}, Number{50});
BEAST_EXPECT(xrpl::cash_basis::loanVaultExposure(sle) == Number{800});
BEAST_EXPECT(
xrpl::cash_basis::loanVaultExposure(LoanEntryR(sle, *env.current())) == Number{800});
}
void
@@ -1752,8 +1758,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(legacyVault, sle) ==
xrpl::instant_recognition::loanVaultExposure(sle));
loanVaultExposure(legacyVault, LoanEntryR(sle, *env.current())) ==
xrpl::instant_recognition::loanVaultExposure(LoanEntryR(sle, *env.current())));
}
{
@@ -1764,7 +1770,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(cashBasisVault, LoanEntryR(sle, *env.current())) ==
xrpl::cash_basis::loanVaultExposure(LoanEntryR(sle, *env.current())));
}
}

View File

@@ -17,6 +17,7 @@
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/json/json_value.h>
#include <xrpl/ledger/entries/LoanEntry.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
@@ -144,7 +145,7 @@ private:
auto const loanSle = env.le(loanKeylet);
if (!BEAST_EXPECT(loanSle))
return;
auto const state = constructLoanState(loanSle);
auto const state = LoanEntryR(loanSle, *env.current()).state();
log << "Loan state:" << std::endl;
log << " ValueOutstanding: " << state.valueOutstanding << std::endl;

View File

@@ -26,6 +26,7 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/json/json_value.h>
#include <xrpl/ledger/entries/LoanEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
@@ -428,7 +429,7 @@ protected:
env.test.BEAST_EXPECT(loan->at(sfPeriodicPayment) == periodicPayment);
env.test.BEAST_EXPECT(loan->at(sfFlags) == flags);
auto const ls = constructLoanState(loan);
auto const ls = LoanEntryR(loan, *env.current()).state();
auto const interestRate = TenthBips32{loan->at(sfInterestRate)};
auto const paymentInterval = loan->at(sfPaymentInterval);
@@ -1254,7 +1255,7 @@ protected:
// No reason for this not to exist
return;
}
auto const current = constructLoanState(loanSle);
auto const current = LoanEntryR(loanSle, *env.current()).state();
auto const errors = nextTrueState - current;
log << currencyLabel << " Loan balances: "
<< "\n\tAmount taken: " << paymentComponents.trackedValueDelta
@@ -2873,7 +2874,7 @@ protected:
bool const impairAllowed = BEAST_EXPECT(loanSle) &&
canImpairLoan(env, broker, state) &&
(!env.current()->rules().enabled(fixCleanup3_4_0) ||
isPaymentLate(*env.current(), loanSle));
isPaymentLate(*env.current(), LoanEntryR(loanSle, *env.current())));
if (impairAllowed)
{
// Making a payment will unimpair the loan

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>