mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Convert LoanSet to precomputed all values
- Tests are not expected to pass
This commit is contained in:
@@ -561,11 +561,12 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({
|
||||
{sfPaymentInterval, soeREQUIRED},
|
||||
{sfGracePeriod, soeREQUIRED},
|
||||
{sfPeriodicPayment, soeREQUIRED},
|
||||
{sfPreviousPaymentDate, soeREQUIRED},
|
||||
{sfPreviousPaymentDate, soeDEFAULT},
|
||||
{sfNextPaymentDueDate, soeREQUIRED},
|
||||
{sfPaymentRemaining, soeREQUIRED},
|
||||
{sfPrincipalOutstanding, soeREQUIRED},
|
||||
{sfPaymentRemaining, soeDEFAULT},
|
||||
{sfPrincipalOutstanding, soeDEFAULT},
|
||||
{sfTotalValueOutstanding, soeDEFAULT},
|
||||
{sfInterestOwed, soeDEFAULT},
|
||||
// Based on the original principal borrowed, used for
|
||||
// rounding calculated values so they are all on a
|
||||
// consistent scale - that is, they all have the same
|
||||
|
||||
@@ -241,6 +241,8 @@ TYPED_SFIELD(sfPrincipalOutstanding, NUMBER, 13)
|
||||
TYPED_SFIELD(sfPrincipalRequested, NUMBER, 14)
|
||||
TYPED_SFIELD(sfTotalValueOutstanding, NUMBER, 15)
|
||||
TYPED_SFIELD(sfPeriodicPayment, NUMBER, 16)
|
||||
TYPED_SFIELD(sfReferencePrincipal, NUMBER, 17)
|
||||
TYPED_SFIELD(sfInterestOwed, NUMBER, 18)
|
||||
|
||||
// int32
|
||||
TYPED_SFIELD(sfLoanScale, INT32, 1)
|
||||
|
||||
@@ -402,6 +402,7 @@ class Loan_test : public beast::unit_test::suite
|
||||
BrokerInfo const& broker,
|
||||
LoanState const& state)
|
||||
{
|
||||
#if LOANCOMPLETE
|
||||
if (auto const brokerSle = env.le(keylet::loanbroker(broker.brokerID));
|
||||
BEAST_EXPECT(brokerSle))
|
||||
{
|
||||
@@ -430,6 +431,7 @@ class Loan_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,16 @@ struct PreflightContext;
|
||||
bool
|
||||
checkLendingProtocolDependencies(PreflightContext const& ctx);
|
||||
|
||||
struct PaymentParts
|
||||
{
|
||||
Number rawInterest;
|
||||
Number rawPrincipal;
|
||||
Number roundedInterest;
|
||||
Number roundedPrincipal;
|
||||
Number roundedPayment;
|
||||
bool final = false;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
// These functions should rarely be used directly. More often, the ultimate
|
||||
// result needs to be roundToAsset'd.
|
||||
@@ -74,11 +84,104 @@ minusManagementFee(Number const& value, TenthBips32 managementFeeRate)
|
||||
return tenthBipsOfValue(value, tenthBipsPerUnity - managementFeeRate);
|
||||
}
|
||||
|
||||
template <AssetType A>
|
||||
PaymentParts
|
||||
computePaymentParts(
|
||||
A const& asset,
|
||||
std::int32_t scale,
|
||||
Number const& totalValueOutstanding,
|
||||
Number const& principalOutstanding,
|
||||
Number const& referencePrincipal,
|
||||
Number const& periodicPayment,
|
||||
Number const& periodicRate,
|
||||
std::uint32_t paymentRemaining)
|
||||
{
|
||||
/*
|
||||
* This function is derived from the XLS-66 spec, section 3.2.4.1.1 (Regular
|
||||
* Payment)
|
||||
*/
|
||||
XRPL_ASSERT_PARTS(
|
||||
isRounded(asset, totalValueOutstanding, scale) &&
|
||||
isRounded(asset, principalOutstanding, scale),
|
||||
"ripple::detail::computePaymentParts",
|
||||
"Outstanding values are rounded");
|
||||
auto const roundedPeriodicPayment =
|
||||
roundToAsset(asset, periodicPayment, scale, Number::upward);
|
||||
if (paymentRemaining == 1 || totalValueOutstanding <= periodicPayment)
|
||||
{
|
||||
// If there's only one payment left, we need to pay off the principal.
|
||||
//
|
||||
// The totalValueOutstanding should never be less than the
|
||||
// periodicPayment until the last scheduled payment, but if it ever is,
|
||||
// make it the last payment.
|
||||
Number rawInterest = totalValueOutstanding - referencePrincipal;
|
||||
Number roundedInterest = totalValueOutstanding - principalOutstanding;
|
||||
|
||||
// This is only expected to be true on the last payment
|
||||
XRPL_ASSERT_PARTS(
|
||||
rawInterest + referencePrincipal ==
|
||||
roundedInterest + principalOutstanding,
|
||||
"ripple::detail::computePaymentParts",
|
||||
"last payment is complete");
|
||||
|
||||
Number const interest = totalValueOutstanding - principalOutstanding;
|
||||
return {
|
||||
.rawInterest = rawInterest,
|
||||
.rawPrincipal = referencePrincipal,
|
||||
.roundedInterest = roundedInterest,
|
||||
.roundedPrincipal = principalOutstanding,
|
||||
.roundedPayment = roundedPeriodicPayment,
|
||||
.final = true};
|
||||
}
|
||||
/*
|
||||
* From the spec, once the periodicPayment is computed:
|
||||
*
|
||||
* The principal and interest portions can be derived as follows:
|
||||
* interest = principalOutstanding * periodicRate
|
||||
* principal = periodicPayment - interest
|
||||
*
|
||||
* Because those values deal with funds, they need to be rounded.
|
||||
*/
|
||||
Number const rawInterest = referencePrincipal * periodicRate;
|
||||
Number const rawPrincipal = periodicPayment - rawInterest;
|
||||
XRPL_ASSERT_PARTS(
|
||||
rawInterest >= 0,
|
||||
"ripple::detail::computePaymentParts",
|
||||
"valid raw interest");
|
||||
XRPL_ASSERT_PARTS(
|
||||
rawPrincipal > 0 && rawPrincipal <= referencePrincipal,
|
||||
"ripple::detail::computePaymentParts",
|
||||
"valid raw principal");
|
||||
|
||||
Number const roundedInterest =
|
||||
roundToAsset(asset, rawInterest, scale, Number::upward);
|
||||
Number const roundedPrincipal = roundedPeriodicPayment - roundedInterest;
|
||||
XRPL_ASSERT_PARTS(
|
||||
roundedInterest >= 0,
|
||||
"ripple::detail::computePaymentParts",
|
||||
"valid rounded interest");
|
||||
XRPL_ASSERT_PARTS(
|
||||
roundedPrincipal >= 0 && roundedPrincipal <= principalOutstanding,
|
||||
"ripple::detail::computePaymentParts",
|
||||
"valid rounded principal");
|
||||
XRPL_ASSERT_PARTS(
|
||||
isRounded(asset, roundedPrincipal, scale),
|
||||
"ripple::detail::computePaymentParts",
|
||||
"principal is rounded");
|
||||
|
||||
return {
|
||||
.rawInterest = rawInterest,
|
||||
.rawPrincipal = rawPrincipal,
|
||||
.roundedInterest = roundedInterest,
|
||||
.roundedPrincipal = roundedPrincipal,
|
||||
.roundedPayment = roundedPeriodicPayment};
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <AssetType A>
|
||||
Number
|
||||
valueMinusManagementFee(
|
||||
valueMinusFee(
|
||||
A const& asset,
|
||||
Number const& value,
|
||||
TenthBips32 managementFeeRate,
|
||||
@@ -88,6 +191,90 @@ valueMinusManagementFee(
|
||||
asset, detail::minusManagementFee(value, managementFeeRate), scale);
|
||||
}
|
||||
|
||||
struct LoanProperties
|
||||
{
|
||||
Number periodicPayment;
|
||||
Number totalValueOutstanding;
|
||||
Number interestOwedToVault;
|
||||
std::int32_t loanScale;
|
||||
Number firstPaymentPrincipal;
|
||||
};
|
||||
|
||||
template <AssetType A>
|
||||
LoanProperties
|
||||
computeLoanProperties(
|
||||
A const& asset,
|
||||
Number const& principalOutstanding,
|
||||
Number const& referencePrincipal,
|
||||
TenthBips32 interestRate,
|
||||
std::uint32_t paymentInterval,
|
||||
std::uint32_t paymentsRemaining,
|
||||
TenthBips32 managementFeeRate)
|
||||
{
|
||||
auto const periodicRate =
|
||||
detail::loanPeriodicRate(interestRate, paymentInterval);
|
||||
auto const periodicPayment = detail::loanPeriodicPayment(
|
||||
principalOutstanding, periodicRate, paymentsRemaining);
|
||||
Number const totalValueOutstanding = [&]() {
|
||||
NumberRoundModeGuard mg(Number::to_nearest);
|
||||
// Use STAmount's internal rounding instead of roundToAsset, because
|
||||
// we're going to use this result to determine the scale for all the
|
||||
// other rounding.
|
||||
return STAmount{
|
||||
asset,
|
||||
/*
|
||||
* This formula is from the XLS-66 spec, section 3.2.4.2 (Total
|
||||
* Loan Value Calculation), specifically "totalValueOutstanding
|
||||
* = ..."
|
||||
*/
|
||||
periodicPayment * paymentsRemaining};
|
||||
}();
|
||||
// Base the loan scale on the total value, since that's going to be the
|
||||
// biggest number involved
|
||||
auto const loanScale = totalValueOutstanding.exponent();
|
||||
|
||||
auto const firstPaymentPrincipal = [&]() {
|
||||
// Compute the unrounded parts for the first payment. Ensure that the
|
||||
// principal payment will actually change the principal.
|
||||
auto const paymentParts = detail::computePaymentParts(
|
||||
asset,
|
||||
loanScale,
|
||||
totalValueOutstanding,
|
||||
principalOutstanding,
|
||||
referencePrincipal,
|
||||
periodicPayment,
|
||||
periodicRate,
|
||||
paymentsRemaining);
|
||||
|
||||
// We only care about the unrounded principal part. It needs to be large
|
||||
// enough that it will affect the reference principal.
|
||||
auto const remaining = referencePrincipal - paymentParts.rawPrincipal;
|
||||
if (remaining == referencePrincipal)
|
||||
// No change, so the first payment effectively pays no principal.
|
||||
// Whether that's a problem is left to the caller.
|
||||
return Number{0};
|
||||
return paymentParts.rawPrincipal;
|
||||
}();
|
||||
|
||||
auto const interestOwedToVault = valueMinusFee(
|
||||
asset,
|
||||
/*
|
||||
* This formula is from the XLS-66 spec, section 3.2.4.2 (Total Loan
|
||||
* Value Calculation), specifically "totalInterestOutstanding = ..."
|
||||
*/
|
||||
totalValueOutstanding - principalOutstanding,
|
||||
managementFeeRate,
|
||||
loanScale);
|
||||
|
||||
return LoanProperties{
|
||||
.periodicPayment = periodicPayment,
|
||||
.totalValueOutstanding = totalValueOutstanding,
|
||||
.interestOwedToVault = interestOwedToVault,
|
||||
.loanScale = loanScale,
|
||||
.firstPaymentPrincipal = firstPaymentPrincipal};
|
||||
}
|
||||
|
||||
#if LOANCOMPLETE
|
||||
inline Number
|
||||
loanPeriodicRate(TenthBips32 interestRate, std::uint32_t paymentInterval)
|
||||
{
|
||||
@@ -211,6 +398,7 @@ loanTotalInterestOutstanding(
|
||||
paymentInterval,
|
||||
paymentsRemaining));
|
||||
}
|
||||
#endif
|
||||
|
||||
template <AssetType A>
|
||||
Number
|
||||
@@ -220,10 +408,11 @@ loanInterestOutstandingMinusFee(
|
||||
TenthBips32 managementFeeRate,
|
||||
std::int32_t scale)
|
||||
{
|
||||
return valueMinusManagementFee(
|
||||
return valueMinusFee(
|
||||
asset, totalInterestOutstanding, managementFeeRate, scale);
|
||||
}
|
||||
|
||||
#if LOANCOMPLETE
|
||||
template <AssetType A>
|
||||
Number
|
||||
loanInterestOutstandingMinusFee(
|
||||
@@ -247,6 +436,7 @@ loanInterestOutstandingMinusFee(
|
||||
managementFeeRate,
|
||||
scale);
|
||||
}
|
||||
#endif
|
||||
|
||||
template <AssetType A>
|
||||
Number
|
||||
@@ -278,98 +468,6 @@ isRounded(A const& asset, Number const& value, std::int32_t scale)
|
||||
roundToAsset(asset, value, scale, Number::upward) == value;
|
||||
}
|
||||
|
||||
struct PaymentParts
|
||||
{
|
||||
Number interest;
|
||||
Number principal;
|
||||
Number fee;
|
||||
};
|
||||
|
||||
template <AssetType A>
|
||||
PaymentParts
|
||||
computePaymentParts(
|
||||
A const& asset,
|
||||
std::int32_t scale,
|
||||
Number const& totalValueOutstanding,
|
||||
Number const& principalOutstanding,
|
||||
Number const& periodicPaymentAmount,
|
||||
Number const& serviceFee,
|
||||
Number const& periodicRate,
|
||||
std::uint32_t paymentRemaining)
|
||||
{
|
||||
/*
|
||||
* This function is derived from the XLS-66 spec, section 3.2.4.1.1 (Regular
|
||||
* Payment)
|
||||
*/
|
||||
XRPL_ASSERT_PARTS(
|
||||
isRounded(asset, totalValueOutstanding, scale) &&
|
||||
isRounded(asset, principalOutstanding, scale) &&
|
||||
isRounded(asset, periodicPaymentAmount, scale),
|
||||
"ripple::computePaymentParts",
|
||||
"Asset values are rounded");
|
||||
Number const roundedFee = roundToAsset(asset, serviceFee, scale);
|
||||
if (paymentRemaining == 1 || periodicPaymentAmount > totalValueOutstanding)
|
||||
{
|
||||
// If there's only one payment left, we need to pay off the principal.
|
||||
Number const interest = totalValueOutstanding - principalOutstanding;
|
||||
return {
|
||||
.interest = interest,
|
||||
.principal = principalOutstanding,
|
||||
.fee = roundedFee};
|
||||
}
|
||||
/*
|
||||
* From the spec, once the periodicPayment is computed:
|
||||
*
|
||||
* The principal and interest portions can be derived as follows:
|
||||
* interest = principalOutstanding * periodicRate
|
||||
* principal = periodicPayment - interest
|
||||
*
|
||||
* Because those values deal with funds, they need to be rounded.
|
||||
*/
|
||||
Number const interest = roundToAsset(
|
||||
asset, principalOutstanding * periodicRate, scale, Number::upward);
|
||||
XRPL_ASSERT(
|
||||
interest >= 0,
|
||||
"ripple::detail::computePeriodicPayment : valid interest");
|
||||
|
||||
// To compute the principal using the above formulas, use the rounded
|
||||
// payment amount, ensuring that some principal is paid regardless of any
|
||||
// other results.
|
||||
auto const roundedPayment = [&]() {
|
||||
auto roundedPayment =
|
||||
roundToAsset(asset, periodicPaymentAmount, scale, Number::upward);
|
||||
if (roundedPayment > interest)
|
||||
return roundedPayment;
|
||||
auto newPayment = roundedPayment;
|
||||
if (asset.native() || !asset.template holds<Issue>())
|
||||
{
|
||||
// integral types, just add one
|
||||
++newPayment;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Non-integral types: IOU. Add "dust" that will not be lost in
|
||||
// rounding.
|
||||
auto const epsilon = Number{1, scale - 14};
|
||||
newPayment += epsilon;
|
||||
}
|
||||
roundedPayment = roundToAsset(asset, newPayment, scale);
|
||||
XRPL_ASSERT_PARTS(
|
||||
roundedPayment == newPayment,
|
||||
"ripple::computePaymentParts",
|
||||
"epsilon preserved in rounding");
|
||||
return roundedPayment;
|
||||
}();
|
||||
Number const principal =
|
||||
roundToAsset(asset, roundedPayment - interest, scale);
|
||||
XRPL_ASSERT_PARTS(
|
||||
principal > 0 && principal <= principalOutstanding,
|
||||
"ripple::computePaymentParts",
|
||||
"valid principal");
|
||||
|
||||
return {.interest = interest, .principal = principal, .fee = roundedFee};
|
||||
}
|
||||
|
||||
// This structure is explained in the XLS-66 spec, section 3.2.4.4 (Failure
|
||||
// Conditions)
|
||||
struct LoanPaymentParts
|
||||
@@ -420,6 +518,8 @@ handleLatePayment(
|
||||
STAmount const& amount,
|
||||
beast::Journal j)
|
||||
{
|
||||
return Unexpected(temDISABLED);
|
||||
#if LOANCOMPLETE
|
||||
if (!hasExpired(view, nextDueDateProxy))
|
||||
return Unexpected(tesSUCCESS);
|
||||
|
||||
@@ -466,6 +566,7 @@ handleLatePayment(
|
||||
.interestPaid = late.interest,
|
||||
.valueChange = latePaymentInterest,
|
||||
.feePaid = late.fee};
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Handle possible full payments.
|
||||
|
||||
@@ -361,6 +361,8 @@ LoanManage::unimpairLoan(
|
||||
TER
|
||||
LoanManage::doApply()
|
||||
{
|
||||
return temDISABLED;
|
||||
#if LOANCOMPLETE
|
||||
auto const& tx = ctx_.tx;
|
||||
auto& view = ctx_.view();
|
||||
|
||||
@@ -435,6 +437,7 @@ LoanManage::doApply()
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
#endif
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -285,10 +285,7 @@ LoanSet::doApply()
|
||||
{
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
}
|
||||
auto const principalRequested = [&](Number const& requested) {
|
||||
return roundToAsset(vaultAsset, requested, requested.exponent());
|
||||
}(tx[sfPrincipalRequested]);
|
||||
auto const loanScale = principalRequested.exponent();
|
||||
auto const principalRequested = tx[sfPrincipalRequested];
|
||||
|
||||
if (auto const assetsAvailable = vaultSle->at(sfAssetsAvailable);
|
||||
assetsAvailable < principalRequested)
|
||||
@@ -300,53 +297,68 @@ LoanSet::doApply()
|
||||
|
||||
TenthBips32 const interestRate{tx[~sfInterestRate].value_or(0)};
|
||||
|
||||
auto const originationFee = roundToAsset(
|
||||
vaultAsset, tx[~sfLoanOriginationFee].value_or(Number{}), loanScale);
|
||||
auto const loanAssetsToBorrower = principalRequested - originationFee;
|
||||
|
||||
auto const paymentInterval =
|
||||
tx[~sfPaymentInterval].value_or(defaultPaymentInterval);
|
||||
auto const paymentTotal = tx[~sfPaymentTotal].value_or(defaultPaymentTotal);
|
||||
|
||||
auto const periodicRate = loanPeriodicRate(interestRate, paymentInterval);
|
||||
auto const periodicPayment = loanPeriodicPayment(
|
||||
vaultAsset, principalRequested, periodicRate, paymentTotal, loanScale);
|
||||
|
||||
auto const totalValueOutstanding = loanTotalValueOutstanding(
|
||||
vaultAsset, loanScale, periodicPayment, paymentTotal);
|
||||
auto const properties = computeLoanProperties(
|
||||
vaultAsset,
|
||||
principalRequested,
|
||||
principalRequested,
|
||||
interestRate,
|
||||
paymentInterval,
|
||||
paymentTotal,
|
||||
TenthBips32{brokerSle->at(sfManagementFeeRate)});
|
||||
|
||||
if (properties.firstPaymentPrincipal <= 0)
|
||||
{
|
||||
// Check that some principal is paid each period. Since the first
|
||||
// payment pays the least principal, if it's good, they'll all be good.
|
||||
auto const paymentParts = computePaymentParts(
|
||||
vaultAsset,
|
||||
loanScale,
|
||||
totalValueOutstanding,
|
||||
principalRequested,
|
||||
periodicPayment,
|
||||
tx[~sfLoanServiceFee].value_or(Number{}),
|
||||
periodicRate,
|
||||
paymentTotal);
|
||||
|
||||
if (paymentParts.principal <= 0)
|
||||
{
|
||||
JLOG(j_.warn()) << "Loan is unable to pay principal.";
|
||||
return tecLIMIT_EXCEEDED;
|
||||
}
|
||||
// Check that some reference principal is paid each period. Since the
|
||||
// first payment pays the least principal, if it's good, they'll all be
|
||||
// good. Note that the outstanding principal is rounded, and may not
|
||||
// change right away.
|
||||
JLOG(j_.warn()) << "Loan is unable to pay principal.";
|
||||
return tecLIMIT_EXCEEDED;
|
||||
}
|
||||
// Check that the other computed values are valid
|
||||
if (properties.interestOwedToVault < 0 ||
|
||||
properties.totalValueOutstanding <= 0 ||
|
||||
properties.periodicPayment <= 0)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.warn())
|
||||
<< "Computed loan properties are invalid. Does not compute.";
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
TenthBips32 const managementFeeRate{brokerSle->at(sfManagementFeeRate)};
|
||||
// Check that relevant values won't lose precision
|
||||
{
|
||||
static std::map<std::string, OptionaledField<STNumber>> const
|
||||
valueFields{
|
||||
{"Principal Requested", ~sfPrincipalRequested},
|
||||
{"Origination fee", ~sfLoanOriginationFee},
|
||||
{"Service fee", ~sfLoanServiceFee},
|
||||
{"Late Payment fee", ~sfLatePaymentFee},
|
||||
{"Close Payment fee", ~sfClosePaymentFee}
|
||||
// Overpayment fee is really a rate. Don't include it.
|
||||
};
|
||||
for (auto const& [name, field] : valueFields)
|
||||
{
|
||||
if (auto const value = tx[field];
|
||||
value && !isRounded(vaultAsset, *value, properties.loanScale))
|
||||
{
|
||||
JLOG(j_.warn()) << name << " has too much precision.";
|
||||
return tecPRECISION_LOSS;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto const originationFee = tx[~sfLoanOriginationFee].value_or(Number{});
|
||||
|
||||
auto const totalInterestOwedToVault = [&]() {
|
||||
auto const totalInterestOutstanding = loanTotalInterestOutstanding(
|
||||
principalRequested, totalValueOutstanding);
|
||||
auto const loanAssetsToBorrower = principalRequested - originationFee;
|
||||
|
||||
return loanInterestOutstandingMinusFee(
|
||||
vaultAsset, totalInterestOutstanding, managementFeeRate, loanScale);
|
||||
}();
|
||||
|
||||
auto const newDebtTotal = brokerSle->at(sfDebtTotal) + principalRequested +
|
||||
totalInterestOwedToVault;
|
||||
auto const newDebtDelta =
|
||||
principalRequested + properties.interestOwedToVault;
|
||||
auto const newDebtTotal = brokerSle->at(sfDebtTotal) + newDebtDelta;
|
||||
if (auto const debtMaximum = brokerSle->at(sfDebtMaximum);
|
||||
debtMaximum != 0 && debtMaximum < newDebtTotal)
|
||||
{
|
||||
@@ -458,7 +470,10 @@ LoanSet::doApply()
|
||||
setLoanField(~sfGracePeriod, defaultGracePeriod);
|
||||
// Set dynamic / computed fields to their initial values
|
||||
loan->at(sfPrincipalOutstanding) = principalRequested;
|
||||
loan->at(sfTotalValueOutstanding) = totalValueOutstanding;
|
||||
loan->at(sfReferencePrincipal) = principalRequested;
|
||||
loan->at(sfPeriodicPayment) = properties.periodicPayment;
|
||||
loan->at(sfTotalValueOutstanding) = properties.totalValueOutstanding;
|
||||
loan->at(sfInterestOwed) = properties.interestOwedToVault;
|
||||
loan->at(sfPreviousPaymentDate) = 0;
|
||||
loan->at(sfNextPaymentDueDate) = startDate + paymentInterval;
|
||||
loan->at(sfPaymentRemaining) = paymentTotal;
|
||||
@@ -466,7 +481,7 @@ LoanSet::doApply()
|
||||
|
||||
// Update the balances in the vault
|
||||
vaultSle->at(sfAssetsAvailable) -= principalRequested;
|
||||
vaultSle->at(sfAssetsTotal) += totalInterestOwedToVault;
|
||||
vaultSle->at(sfAssetsTotal) += properties.interestOwedToVault;
|
||||
XRPL_ASSERT_PARTS(
|
||||
*vaultSle->at(sfAssetsAvailable) <= *vaultSle->at(sfAssetsTotal),
|
||||
"ripple::LoanSet::doApply",
|
||||
@@ -474,7 +489,7 @@ LoanSet::doApply()
|
||||
view.update(vaultSle);
|
||||
|
||||
// Update the balances in the loan broker
|
||||
brokerSle->at(sfDebtTotal) += principalRequested + totalInterestOwedToVault;
|
||||
brokerSle->at(sfDebtTotal) += newDebtDelta;
|
||||
// The broker's owner count is solely for the number of outstanding loans,
|
||||
// and is distinct from the broker's pseudo-account's owner count
|
||||
adjustOwnerCount(view, brokerSle, 1, j_);
|
||||
|
||||
Reference in New Issue
Block a user