mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-26 14:05:51 +00:00
Start converting Loans to use fixed payments and track value
- Not expected to build
This commit is contained in:
@@ -66,15 +66,15 @@ public:
|
|||||||
static int const cMaxOffset = 80;
|
static int const cMaxOffset = 80;
|
||||||
|
|
||||||
// Maximum native value supported by the code
|
// Maximum native value supported by the code
|
||||||
static std::uint64_t const cMinValue = 1000000000000000ull;
|
static std::uint64_t const cMinValue = 1'000'000'000'000'000ull;
|
||||||
static std::uint64_t const cMaxValue = 9999999999999999ull;
|
static std::uint64_t const cMaxValue = 9'999'999'999'999'999ull;
|
||||||
static std::uint64_t const cMaxNative = 9000000000000000000ull;
|
static std::uint64_t const cMaxNative = 9'000'000'000'000'000'000ull;
|
||||||
|
|
||||||
// Max native value on network.
|
// Max native value on network.
|
||||||
static std::uint64_t const cMaxNativeN = 100000000000000000ull;
|
static std::uint64_t const cMaxNativeN = 100'000'000'000'000'000ull;
|
||||||
static std::uint64_t const cIssuedCurrency = 0x8000000000000000ull;
|
static std::uint64_t const cIssuedCurrency = 0x8'000'000'000'000'000ull;
|
||||||
static std::uint64_t const cPositive = 0x4000000000000000ull;
|
static std::uint64_t const cPositive = 0x4'000'000'000'000'000ull;
|
||||||
static std::uint64_t const cMPToken = 0x2000000000000000ull;
|
static std::uint64_t const cMPToken = 0x2'000'000'000'000'000ull;
|
||||||
static std::uint64_t const cValueMask = ~(cPositive | cMPToken);
|
static std::uint64_t const cValueMask = ~(cPositive | cMPToken);
|
||||||
|
|
||||||
static std::uint64_t const uRateOne;
|
static std::uint64_t const uRateOne;
|
||||||
@@ -695,21 +695,22 @@ divRoundStrict(
|
|||||||
std::uint64_t
|
std::uint64_t
|
||||||
getRate(STAmount const& offerOut, STAmount const& offerIn);
|
getRate(STAmount const& offerOut, STAmount const& offerIn);
|
||||||
|
|
||||||
/** Round an arbitrary precision Amount to the precision of a reference Amount.
|
/** Round an arbitrary precision Amount to the precision of an STAmount that has
|
||||||
|
* a given exponent.
|
||||||
*
|
*
|
||||||
* This is used to ensure that calculations involving IOU amounts do not collect
|
* This is used to ensure that calculations involving IOU amounts do not collect
|
||||||
* dust beyond the precision of the reference value.
|
* dust beyond the precision of the reference value.
|
||||||
*
|
*
|
||||||
* @param value The value to be rounded
|
* @param value The value to be rounded
|
||||||
* @param referenceValue A reference value to establish the precision limit of
|
* @param scale An exponent value to establish the precision limit of
|
||||||
* `value`. Should be larger than `value`.
|
* `value`. Should be larger than `value.exponent()`.
|
||||||
* @param rounding Optional Number rounding mode
|
* @param rounding Optional Number rounding mode
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
STAmount
|
STAmount
|
||||||
roundToReference(
|
roundToScale(
|
||||||
STAmount const value,
|
STAmount value,
|
||||||
STAmount referenceValue,
|
std::int32_t scale,
|
||||||
Number::rounding_mode rounding = Number::getround());
|
Number::rounding_mode rounding = Number::getround());
|
||||||
|
|
||||||
/** Round an arbitrary precision Number to the precision of a given Asset.
|
/** Round an arbitrary precision Number to the precision of a given Asset.
|
||||||
@@ -720,9 +721,8 @@ roundToReference(
|
|||||||
*
|
*
|
||||||
* @param asset The relevant asset
|
* @param asset The relevant asset
|
||||||
* @param value The value to be rounded
|
* @param value The value to be rounded
|
||||||
* @param referenceValue Only relevant to IOU assets. A reference value to
|
* @param scale Only relevant to IOU assets. An exponent value to establish the
|
||||||
* establish the precision limit of `value`. Should be larger than
|
* precision limit of `value`. Should be larger than `value.exponent()`.
|
||||||
* `value`.
|
|
||||||
* @param rounding Optional Number rounding mode
|
* @param rounding Optional Number rounding mode
|
||||||
*/
|
*/
|
||||||
template <AssetType A>
|
template <AssetType A>
|
||||||
@@ -730,7 +730,7 @@ Number
|
|||||||
roundToAsset(
|
roundToAsset(
|
||||||
A const& asset,
|
A const& asset,
|
||||||
Number const& value,
|
Number const& value,
|
||||||
Number const& referenceValue,
|
std::int32_t scale,
|
||||||
Number::rounding_mode rounding = Number::getround())
|
Number::rounding_mode rounding = Number::getround())
|
||||||
{
|
{
|
||||||
NumberRoundModeGuard mg(rounding);
|
NumberRoundModeGuard mg(rounding);
|
||||||
@@ -739,7 +739,7 @@ roundToAsset(
|
|||||||
return ret;
|
return ret;
|
||||||
// Not that the ctor will round integral types (XRP, MPT) via canonicalize,
|
// Not that the ctor will round integral types (XRP, MPT) via canonicalize,
|
||||||
// so no extra work is needed for those.
|
// so no extra work is needed for those.
|
||||||
return roundToReference(ret, STAmount{asset, referenceValue});
|
return roundToScale(ret, scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -548,25 +548,29 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({
|
|||||||
{sfLoanBrokerID, soeREQUIRED},
|
{sfLoanBrokerID, soeREQUIRED},
|
||||||
{sfLoanSequence, soeREQUIRED},
|
{sfLoanSequence, soeREQUIRED},
|
||||||
{sfBorrower, soeREQUIRED},
|
{sfBorrower, soeREQUIRED},
|
||||||
{sfLoanOriginationFee, soeREQUIRED},
|
{sfLoanOriginationFee, soeDEFAULT},
|
||||||
{sfLoanServiceFee, soeREQUIRED},
|
{sfLoanServiceFee, soeDEFAULT},
|
||||||
{sfLatePaymentFee, soeREQUIRED},
|
{sfLatePaymentFee, soeDEFAULT},
|
||||||
{sfClosePaymentFee, soeREQUIRED},
|
{sfClosePaymentFee, soeDEFAULT},
|
||||||
{sfOverpaymentFee, soeREQUIRED},
|
{sfOverpaymentFee, soeDEFAULT},
|
||||||
{sfInterestRate, soeREQUIRED},
|
{sfInterestRate, soeDEFAULT},
|
||||||
{sfLateInterestRate, soeREQUIRED},
|
{sfLateInterestRate, soeDEFAULT},
|
||||||
{sfCloseInterestRate, soeREQUIRED},
|
{sfCloseInterestRate, soeDEFAULT},
|
||||||
{sfOverpaymentInterestRate, soeREQUIRED},
|
{sfOverpaymentInterestRate, soeDEFAULT},
|
||||||
{sfStartDate, soeREQUIRED},
|
{sfStartDate, soeREQUIRED},
|
||||||
{sfPaymentInterval, soeREQUIRED},
|
{sfPaymentInterval, soeREQUIRED},
|
||||||
{sfGracePeriod, soeREQUIRED},
|
{sfGracePeriod, soeREQUIRED},
|
||||||
|
{sfPeriodicPayment, soeREQUIRED},
|
||||||
{sfPreviousPaymentDate, soeREQUIRED},
|
{sfPreviousPaymentDate, soeREQUIRED},
|
||||||
{sfNextPaymentDueDate, soeREQUIRED},
|
{sfNextPaymentDueDate, soeREQUIRED},
|
||||||
{sfPaymentRemaining, soeREQUIRED},
|
{sfPaymentRemaining, soeREQUIRED},
|
||||||
{sfPrincipalOutstanding, soeREQUIRED},
|
{sfPrincipalOutstanding, soeREQUIRED},
|
||||||
// Save the original request amount for rounding / scaling of
|
{sfTotalValueOutstanding, soeDEFAULT},
|
||||||
// other computations, particularly for IOUs
|
// Based on the original principal borrowed, used for
|
||||||
{sfPrincipalRequested, soeREQUIRED},
|
// rounding calculated values so they are all on a
|
||||||
|
// consistent scale - that is, they all have the same
|
||||||
|
// number of decimal places after the decimal point.
|
||||||
|
{sfLoanScale, soeDEFAULT},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
#undef EXPAND
|
#undef EXPAND
|
||||||
|
|||||||
@@ -239,12 +239,11 @@ TYPED_SFIELD(sfLatePaymentFee, NUMBER, 11)
|
|||||||
TYPED_SFIELD(sfClosePaymentFee, NUMBER, 12)
|
TYPED_SFIELD(sfClosePaymentFee, NUMBER, 12)
|
||||||
TYPED_SFIELD(sfPrincipalOutstanding, NUMBER, 13)
|
TYPED_SFIELD(sfPrincipalOutstanding, NUMBER, 13)
|
||||||
TYPED_SFIELD(sfPrincipalRequested, NUMBER, 14)
|
TYPED_SFIELD(sfPrincipalRequested, NUMBER, 14)
|
||||||
|
TYPED_SFIELD(sfTotalValueOutstanding, NUMBER, 15)
|
||||||
|
TYPED_SFIELD(sfPeriodicPayment, NUMBER, 16)
|
||||||
|
|
||||||
// int32
|
// int32
|
||||||
// NOTE: Do not use `sfDummyInt32`. It's so far the only use of INT32
|
TYPED_SFIELD(sfLoanScale, INT32, 1)
|
||||||
// in this file and has been defined here for test only.
|
|
||||||
// TODO: Replace `sfDummyInt32` with actually useful field.
|
|
||||||
TYPED_SFIELD(sfDummyInt32, INT32, 1) // for tests only
|
|
||||||
|
|
||||||
// currency amount (common)
|
// currency amount (common)
|
||||||
TYPED_SFIELD(sfAmount, AMOUNT, 1)
|
TYPED_SFIELD(sfAmount, AMOUNT, 1)
|
||||||
|
|||||||
@@ -1510,32 +1510,28 @@ canonicalizeRoundStrict(
|
|||||||
}
|
}
|
||||||
|
|
||||||
STAmount
|
STAmount
|
||||||
roundToReference(
|
roundToScale(STAmount value, std::int32_t scale, Number::rounding_mode rounding)
|
||||||
STAmount const value,
|
|
||||||
STAmount referenceValue,
|
|
||||||
Number::rounding_mode rounding)
|
|
||||||
{
|
{
|
||||||
// Nothing to do for intgral types.
|
// Nothing to do for intgral types.
|
||||||
if (value.asset().native() || !value.asset().holds<Issue>())
|
if (value.asset().native() || !value.asset().holds<Issue>())
|
||||||
return value;
|
return value;
|
||||||
|
|
||||||
// If the value is greater than or equal to the referenceValue (ignoring
|
// If the value's exponent is greater than or equal to the scale, then
|
||||||
// sign), then rounding will do nothing, so just return the value.
|
// rounding will do nothing, and might even lose precision, so just return
|
||||||
if (value.exponent() > referenceValue.exponent() ||
|
// the value.
|
||||||
(value.exponent() == referenceValue.exponent() &&
|
if (value.exponent() >= scale)
|
||||||
value.mantissa() >= referenceValue.mantissa()))
|
|
||||||
return value;
|
return value;
|
||||||
|
|
||||||
if (referenceValue.negative() != value.negative())
|
STAmount referenceValue{
|
||||||
referenceValue.negate();
|
value.asset(), STAmount::cMinValue, scale, value.negative()};
|
||||||
|
|
||||||
NumberRoundModeGuard mg(rounding);
|
NumberRoundModeGuard mg(rounding);
|
||||||
// With an IOU, the total will be truncated to the precision of the
|
// With an IOU, the total will be truncated to the precision of the
|
||||||
// larger value: referenceValue
|
// larger value: referenceValue
|
||||||
STAmount const total = referenceValue + value;
|
value += referenceValue;
|
||||||
// Remove the reference value, and we're left with the rounded value.
|
// Remove the reference value, and we're left with the rounded value.
|
||||||
STAmount const result = total - referenceValue;
|
value -= referenceValue;
|
||||||
return result;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ class Loan_test : public beast::unit_test::suite
|
|||||||
NetClock::time_point startDate = {};
|
NetClock::time_point startDate = {};
|
||||||
std::uint32_t nextPaymentDate = 0;
|
std::uint32_t nextPaymentDate = 0;
|
||||||
std::uint32_t paymentRemaining = 0;
|
std::uint32_t paymentRemaining = 0;
|
||||||
Number const principalRequested = 0;
|
std::int32_t const loanScale = 0;
|
||||||
Number principalOutstanding = 0;
|
Number principalOutstanding = 0;
|
||||||
std::uint32_t flags = 0;
|
std::uint32_t flags = 0;
|
||||||
std::uint32_t paymentInterval = 0;
|
std::uint32_t paymentInterval = 0;
|
||||||
@@ -221,7 +221,7 @@ class Loan_test : public beast::unit_test::suite
|
|||||||
std::uint32_t nextPaymentDate,
|
std::uint32_t nextPaymentDate,
|
||||||
std::uint32_t paymentRemaining,
|
std::uint32_t paymentRemaining,
|
||||||
Number const& principalRequested,
|
Number const& principalRequested,
|
||||||
Number const& principalOutstanding,
|
Number const& loanScale,
|
||||||
std::uint32_t flags) const
|
std::uint32_t flags) const
|
||||||
{
|
{
|
||||||
using namespace jtx;
|
using namespace jtx;
|
||||||
@@ -233,13 +233,12 @@ class Loan_test : public beast::unit_test::suite
|
|||||||
loan->at(sfNextPaymentDueDate) == nextPaymentDate);
|
loan->at(sfNextPaymentDueDate) == nextPaymentDate);
|
||||||
env.test.BEAST_EXPECT(
|
env.test.BEAST_EXPECT(
|
||||||
loan->at(sfPaymentRemaining) == paymentRemaining);
|
loan->at(sfPaymentRemaining) == paymentRemaining);
|
||||||
env.test.BEAST_EXPECT(
|
env.test.BEAST_EXPECT(loan->at(sfLoanScale) == loanScale);
|
||||||
loan->at(sfPrincipalRequested) == principalRequested);
|
|
||||||
env.test.BEAST_EXPECT(
|
env.test.BEAST_EXPECT(
|
||||||
loan->at(sfPrincipalOutstanding) == principalOutstanding);
|
loan->at(sfPrincipalOutstanding) == principalOutstanding);
|
||||||
env.test.BEAST_EXPECT(
|
env.test.BEAST_EXPECT(
|
||||||
loan->at(sfPrincipalRequested) ==
|
loan->at(sfLoanScale) ==
|
||||||
broker.asset(loanAmount).value());
|
broker.asset(loanAmount).value().exponent());
|
||||||
env.test.BEAST_EXPECT(loan->at(sfFlags) == flags);
|
env.test.BEAST_EXPECT(loan->at(sfFlags) == flags);
|
||||||
|
|
||||||
auto const interestRate = TenthBips32{loan->at(sfInterestRate)};
|
auto const interestRate = TenthBips32{loan->at(sfInterestRate)};
|
||||||
@@ -369,7 +368,7 @@ class Loan_test : public beast::unit_test::suite
|
|||||||
.startDate = tp{d{loan->at(sfStartDate)}},
|
.startDate = tp{d{loan->at(sfStartDate)}},
|
||||||
.nextPaymentDate = loan->at(sfNextPaymentDueDate),
|
.nextPaymentDate = loan->at(sfNextPaymentDueDate),
|
||||||
.paymentRemaining = loan->at(sfPaymentRemaining),
|
.paymentRemaining = loan->at(sfPaymentRemaining),
|
||||||
.principalRequested = loan->at(sfPrincipalRequested),
|
.loanScale = loan->at(sfLoanScale),
|
||||||
.principalOutstanding = loan->at(sfPrincipalOutstanding),
|
.principalOutstanding = loan->at(sfPrincipalOutstanding),
|
||||||
.flags = loan->at(sfFlags),
|
.flags = loan->at(sfFlags),
|
||||||
.paymentInterval = loan->at(sfPaymentInterval),
|
.paymentInterval = loan->at(sfPaymentInterval),
|
||||||
@@ -611,7 +610,7 @@ class Loan_test : public beast::unit_test::suite
|
|||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
loan->at(sfNextPaymentDueDate) == startDate + interval);
|
loan->at(sfNextPaymentDueDate) == startDate + interval);
|
||||||
BEAST_EXPECT(loan->at(sfPaymentRemaining) == total);
|
BEAST_EXPECT(loan->at(sfPaymentRemaining) == total);
|
||||||
BEAST_EXPECT(loan->at(sfPrincipalRequested) == principalRequest);
|
BEAST_EXPECT(loan->at(sfLoanScale) == principalRequest.exponent());
|
||||||
BEAST_EXPECT(loan->at(sfPrincipalOutstanding) == principalRequest);
|
BEAST_EXPECT(loan->at(sfPrincipalOutstanding) == principalRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1991,7 +1990,7 @@ class Loan_test : public beast::unit_test::suite
|
|||||||
BEAST_EXPECT(loan[sfPaymentRemaining] == 1);
|
BEAST_EXPECT(loan[sfPaymentRemaining] == 1);
|
||||||
BEAST_EXPECT(loan[sfPreviousPaymentDate] == 0);
|
BEAST_EXPECT(loan[sfPreviousPaymentDate] == 0);
|
||||||
BEAST_EXPECT(loan[sfPrincipalOutstanding] == "1000000000");
|
BEAST_EXPECT(loan[sfPrincipalOutstanding] == "1000000000");
|
||||||
BEAST_EXPECT(loan[sfPrincipalRequested] == "1000000000");
|
BEAST_EXPECT(loan[sfLoanScale] == 0);
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
loan[sfStartDate].asUInt() ==
|
loan[sfStartDate].asUInt() ==
|
||||||
startDate.time_since_epoch().count());
|
startDate.time_since_epoch().count());
|
||||||
@@ -2183,7 +2182,7 @@ class Loan_test : public beast::unit_test::suite
|
|||||||
{
|
{
|
||||||
// Verify the payment decreased the principal
|
// Verify the payment decreased the principal
|
||||||
BEAST_EXPECT(loan->at(sfPaymentRemaining) == numPayments);
|
BEAST_EXPECT(loan->at(sfPaymentRemaining) == numPayments);
|
||||||
BEAST_EXPECT(loan->at(sfPrincipalRequested) == actualPrincipal);
|
BEAST_EXPECT(loan->at(sfLoanScale) == actualPrincipal.exponent());
|
||||||
BEAST_EXPECT(loan->at(sfPrincipalOutstanding) == actualPrincipal);
|
BEAST_EXPECT(loan->at(sfPrincipalOutstanding) == actualPrincipal);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2196,7 +2195,7 @@ class Loan_test : public beast::unit_test::suite
|
|||||||
{
|
{
|
||||||
// Verify the payment decreased the principal
|
// Verify the payment decreased the principal
|
||||||
BEAST_EXPECT(loan->at(sfPaymentRemaining) == numPayments - 1);
|
BEAST_EXPECT(loan->at(sfPaymentRemaining) == numPayments - 1);
|
||||||
BEAST_EXPECT(loan->at(sfPrincipalRequested) == actualPrincipal);
|
BEAST_EXPECT(loan->at(sfLoanScale) == actualPrincipal.exponent());
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
loan->at(sfPrincipalOutstanding) == actualPrincipal - 1);
|
loan->at(sfPrincipalOutstanding) == actualPrincipal - 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -743,63 +743,63 @@ class STParsedJSON_test : public beast::unit_test::suite
|
|||||||
{
|
{
|
||||||
Json::Value j;
|
Json::Value j;
|
||||||
int const minInt32 = -2147483648;
|
int const minInt32 = -2147483648;
|
||||||
j[sfDummyInt32] = minInt32;
|
j[sfLoanScale] = minInt32;
|
||||||
STParsedJSONObject obj("Test", j);
|
STParsedJSONObject obj("Test", j);
|
||||||
BEAST_EXPECT(obj.object.has_value());
|
BEAST_EXPECT(obj.object.has_value());
|
||||||
if (BEAST_EXPECT(obj.object->isFieldPresent(sfDummyInt32)))
|
if (BEAST_EXPECT(obj.object->isFieldPresent(sfLoanScale)))
|
||||||
BEAST_EXPECT(obj.object->getFieldI32(sfDummyInt32) == minInt32);
|
BEAST_EXPECT(obj.object->getFieldI32(sfLoanScale) == minInt32);
|
||||||
}
|
}
|
||||||
|
|
||||||
// max value
|
// max value
|
||||||
{
|
{
|
||||||
Json::Value j;
|
Json::Value j;
|
||||||
int const maxInt32 = 2147483647;
|
int const maxInt32 = 2147483647;
|
||||||
j[sfDummyInt32] = maxInt32;
|
j[sfLoanScale] = maxInt32;
|
||||||
STParsedJSONObject obj("Test", j);
|
STParsedJSONObject obj("Test", j);
|
||||||
BEAST_EXPECT(obj.object.has_value());
|
BEAST_EXPECT(obj.object.has_value());
|
||||||
if (BEAST_EXPECT(obj.object->isFieldPresent(sfDummyInt32)))
|
if (BEAST_EXPECT(obj.object->isFieldPresent(sfLoanScale)))
|
||||||
BEAST_EXPECT(obj.object->getFieldI32(sfDummyInt32) == maxInt32);
|
BEAST_EXPECT(obj.object->getFieldI32(sfLoanScale) == maxInt32);
|
||||||
}
|
}
|
||||||
|
|
||||||
// max uint value
|
// max uint value
|
||||||
{
|
{
|
||||||
Json::Value j;
|
Json::Value j;
|
||||||
unsigned int const maxUInt32 = 2147483647u;
|
unsigned int const maxUInt32 = 2147483647u;
|
||||||
j[sfDummyInt32] = maxUInt32;
|
j[sfLoanScale] = maxUInt32;
|
||||||
STParsedJSONObject obj("Test", j);
|
STParsedJSONObject obj("Test", j);
|
||||||
BEAST_EXPECT(obj.object.has_value());
|
BEAST_EXPECT(obj.object.has_value());
|
||||||
if (BEAST_EXPECT(obj.object->isFieldPresent(sfDummyInt32)))
|
if (BEAST_EXPECT(obj.object->isFieldPresent(sfLoanScale)))
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
obj.object->getFieldI32(sfDummyInt32) ==
|
obj.object->getFieldI32(sfLoanScale) ==
|
||||||
static_cast<int32_t>(maxUInt32));
|
static_cast<int32_t>(maxUInt32));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test with string value
|
// Test with string value
|
||||||
{
|
{
|
||||||
Json::Value j;
|
Json::Value j;
|
||||||
j[sfDummyInt32] = "2147483647";
|
j[sfLoanScale] = "2147483647";
|
||||||
STParsedJSONObject obj("Test", j);
|
STParsedJSONObject obj("Test", j);
|
||||||
BEAST_EXPECT(obj.object.has_value());
|
BEAST_EXPECT(obj.object.has_value());
|
||||||
if (BEAST_EXPECT(obj.object->isFieldPresent(sfDummyInt32)))
|
if (BEAST_EXPECT(obj.object->isFieldPresent(sfLoanScale)))
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
obj.object->getFieldI32(sfDummyInt32) == 2147483647u);
|
obj.object->getFieldI32(sfLoanScale) == 2147483647u);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test with string negative value
|
// Test with string negative value
|
||||||
{
|
{
|
||||||
Json::Value j;
|
Json::Value j;
|
||||||
int value = -2147483648;
|
int value = -2147483648;
|
||||||
j[sfDummyInt32] = std::to_string(value);
|
j[sfLoanScale] = std::to_string(value);
|
||||||
STParsedJSONObject obj("Test", j);
|
STParsedJSONObject obj("Test", j);
|
||||||
BEAST_EXPECT(obj.object.has_value());
|
BEAST_EXPECT(obj.object.has_value());
|
||||||
if (BEAST_EXPECT(obj.object->isFieldPresent(sfDummyInt32)))
|
if (BEAST_EXPECT(obj.object->isFieldPresent(sfLoanScale)))
|
||||||
BEAST_EXPECT(obj.object->getFieldI32(sfDummyInt32) == value);
|
BEAST_EXPECT(obj.object->getFieldI32(sfLoanScale) == value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test out of range value for int32 (negative)
|
// Test out of range value for int32 (negative)
|
||||||
{
|
{
|
||||||
Json::Value j;
|
Json::Value j;
|
||||||
j[sfDummyInt32] = "-2147483649";
|
j[sfLoanScale] = "-2147483649";
|
||||||
STParsedJSONObject obj("Test", j);
|
STParsedJSONObject obj("Test", j);
|
||||||
BEAST_EXPECT(!obj.object.has_value());
|
BEAST_EXPECT(!obj.object.has_value());
|
||||||
}
|
}
|
||||||
@@ -807,7 +807,7 @@ class STParsedJSON_test : public beast::unit_test::suite
|
|||||||
// Test out of range value for int32 (positive)
|
// Test out of range value for int32 (positive)
|
||||||
{
|
{
|
||||||
Json::Value j;
|
Json::Value j;
|
||||||
j[sfDummyInt32] = 2147483648u;
|
j[sfLoanScale] = 2147483648u;
|
||||||
STParsedJSONObject obj("Test", j);
|
STParsedJSONObject obj("Test", j);
|
||||||
BEAST_EXPECT(!obj.object.has_value());
|
BEAST_EXPECT(!obj.object.has_value());
|
||||||
}
|
}
|
||||||
@@ -815,7 +815,7 @@ class STParsedJSON_test : public beast::unit_test::suite
|
|||||||
// Test string value out of range
|
// Test string value out of range
|
||||||
{
|
{
|
||||||
Json::Value j;
|
Json::Value j;
|
||||||
j[sfDummyInt32] = "2147483648";
|
j[sfLoanScale] = "2147483648";
|
||||||
STParsedJSONObject obj("Test", j);
|
STParsedJSONObject obj("Test", j);
|
||||||
BEAST_EXPECT(!obj.object.has_value());
|
BEAST_EXPECT(!obj.object.has_value());
|
||||||
}
|
}
|
||||||
@@ -823,7 +823,7 @@ class STParsedJSON_test : public beast::unit_test::suite
|
|||||||
// Test bad_type (arrayValue)
|
// Test bad_type (arrayValue)
|
||||||
{
|
{
|
||||||
Json::Value j;
|
Json::Value j;
|
||||||
j[sfDummyInt32] = Json::Value(Json::arrayValue);
|
j[sfLoanScale] = Json::Value(Json::arrayValue);
|
||||||
STParsedJSONObject obj("Test", j);
|
STParsedJSONObject obj("Test", j);
|
||||||
BEAST_EXPECT(!obj.object.has_value());
|
BEAST_EXPECT(!obj.object.has_value());
|
||||||
}
|
}
|
||||||
@@ -831,7 +831,7 @@ class STParsedJSON_test : public beast::unit_test::suite
|
|||||||
// Test bad_type (objectValue)
|
// Test bad_type (objectValue)
|
||||||
{
|
{
|
||||||
Json::Value j;
|
Json::Value j;
|
||||||
j[sfDummyInt32] = Json::Value(Json::objectValue);
|
j[sfLoanScale] = Json::Value(Json::objectValue);
|
||||||
STParsedJSONObject obj("Test", j);
|
STParsedJSONObject obj("Test", j);
|
||||||
BEAST_EXPECT(!obj.object.has_value());
|
BEAST_EXPECT(!obj.object.has_value());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,22 +40,100 @@ loanPeriodicRate(TenthBips32 interestRate, std::uint32_t paymentInterval);
|
|||||||
|
|
||||||
Number
|
Number
|
||||||
loanPeriodicPayment(
|
loanPeriodicPayment(
|
||||||
Number principalOutstanding,
|
Number const& principalOutstanding,
|
||||||
Number periodicRate,
|
Number const& periodicRate,
|
||||||
std::uint32_t paymentsRemaining);
|
std::uint32_t paymentsRemaining);
|
||||||
|
|
||||||
Number
|
Number
|
||||||
loanPeriodicPayment(
|
loanPeriodicPayment(
|
||||||
Number principalOutstanding,
|
Number const& principalOutstanding,
|
||||||
TenthBips32 interestRate,
|
TenthBips32 interestRate,
|
||||||
std::uint32_t paymentInterval,
|
std::uint32_t paymentInterval,
|
||||||
std::uint32_t paymentsRemaining);
|
std::uint32_t paymentsRemaining);
|
||||||
|
|
||||||
|
Number
|
||||||
|
loanLatePaymentInterest(
|
||||||
|
Number const& principalOutstanding,
|
||||||
|
TenthBips32 lateInterestRate,
|
||||||
|
NetClock::time_point parentCloseTime,
|
||||||
|
std::uint32_t startDate,
|
||||||
|
std::uint32_t prevPaymentDate);
|
||||||
|
|
||||||
|
Number
|
||||||
|
loanAccruedInterest(
|
||||||
|
Number const& principalOutstanding,
|
||||||
|
Number const& periodicRate,
|
||||||
|
NetClock::time_point parentCloseTime,
|
||||||
|
std::uint32_t startDate,
|
||||||
|
std::uint32_t prevPaymentDate,
|
||||||
|
std::uint32_t paymentInterval);
|
||||||
|
|
||||||
|
inline Number
|
||||||
|
minusManagementFee(Number const& value, TenthBips32 managementFeeRate)
|
||||||
|
{
|
||||||
|
return tenthBipsOfValue(value, tenthBipsPerUnity - managementFeeRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template <AssetType A>
|
||||||
|
Number
|
||||||
|
valueMinusManagementFee(
|
||||||
|
A const& asset,
|
||||||
|
Number const& value,
|
||||||
|
TenthBips32 managementFeeRate,
|
||||||
|
std::int32_t scale)
|
||||||
|
{
|
||||||
|
return roundToAsset(
|
||||||
|
asset, detail::minusManagementFee(value, managementFeeRate), scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
Number
|
||||||
|
loanPeriodicRate(TenthBips32 interestRate, std::uint32_t paymentInterval)
|
||||||
|
{
|
||||||
|
return detail::loanPeriodicRate(interestRate, paymentInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <AssetType A>
|
||||||
|
Number
|
||||||
|
loanPeriodicPayment(
|
||||||
|
A const& asset,
|
||||||
|
Number const& principalOutstanding,
|
||||||
|
Number const& periodicRate,
|
||||||
|
std::uint32_t paymentsRemaining,
|
||||||
|
std::int32_t scale)
|
||||||
|
{
|
||||||
|
return roundToAsset(
|
||||||
|
asset,
|
||||||
|
detail::loanPeriodicPayment(
|
||||||
|
principalOutstanding, periodicRate, paymentsRemaining),
|
||||||
|
scale,
|
||||||
|
Number::upward);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <AssetType A>
|
||||||
|
Number
|
||||||
|
loanPeriodicPayment(
|
||||||
|
A const& asset,
|
||||||
|
Number const& principalOutstanding,
|
||||||
|
TenthBips32 interestRate,
|
||||||
|
std::uint32_t paymentInterval,
|
||||||
|
std::uint32_t paymentsRemaining,
|
||||||
|
std::int32_t scale)
|
||||||
|
{
|
||||||
|
loanPeriodicPayment(
|
||||||
|
asset,
|
||||||
|
principalOutstanding,
|
||||||
|
loanPeriodicRate(interestRate, paymentInterval),
|
||||||
|
paymentsRemaining,
|
||||||
|
scale);
|
||||||
|
}
|
||||||
|
|
||||||
template <AssetType A>
|
template <AssetType A>
|
||||||
Number
|
Number
|
||||||
loanTotalValueOutstanding(
|
loanTotalValueOutstanding(
|
||||||
A asset,
|
A asset,
|
||||||
Number const& originalPrincipal,
|
std::int32_t scale,
|
||||||
Number const& periodicPayment,
|
Number const& periodicPayment,
|
||||||
std::uint32_t paymentsRemaining)
|
std::uint32_t paymentsRemaining)
|
||||||
{
|
{
|
||||||
@@ -66,7 +144,7 @@ loanTotalValueOutstanding(
|
|||||||
* Value Calculation), specifically "totalValueOutstanding = ..."
|
* Value Calculation), specifically "totalValueOutstanding = ..."
|
||||||
*/
|
*/
|
||||||
periodicPayment * paymentsRemaining,
|
periodicPayment * paymentsRemaining,
|
||||||
originalPrincipal,
|
scale,
|
||||||
Number::upward);
|
Number::upward);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +152,7 @@ template <AssetType A>
|
|||||||
Number
|
Number
|
||||||
loanTotalValueOutstanding(
|
loanTotalValueOutstanding(
|
||||||
A asset,
|
A asset,
|
||||||
Number const& originalPrincipal,
|
std::int32_t scale,
|
||||||
Number const& principalOutstanding,
|
Number const& principalOutstanding,
|
||||||
TenthBips32 interestRate,
|
TenthBips32 interestRate,
|
||||||
std::uint32_t paymentInterval,
|
std::uint32_t paymentInterval,
|
||||||
@@ -86,19 +164,21 @@ loanTotalValueOutstanding(
|
|||||||
*/
|
*/
|
||||||
return loanTotalValueOutstanding(
|
return loanTotalValueOutstanding(
|
||||||
asset,
|
asset,
|
||||||
originalPrincipal,
|
scale,
|
||||||
loanPeriodicPayment(
|
loanPeriodicPayment(
|
||||||
|
asset,
|
||||||
principalOutstanding,
|
principalOutstanding,
|
||||||
interestRate,
|
interestRate,
|
||||||
paymentInterval,
|
paymentInterval,
|
||||||
paymentsRemaining),
|
paymentsRemaining,
|
||||||
|
scale),
|
||||||
paymentsRemaining);
|
paymentsRemaining);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Number
|
inline Number
|
||||||
loanTotalInterestOutstanding(
|
loanTotalInterestOutstanding(
|
||||||
Number principalOutstanding,
|
Number const& principalOutstanding,
|
||||||
Number totalValueOutstanding)
|
Number const& totalValueOutstanding)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* This formula is from the XLS-66 spec, section 3.2.4.2 (Total Loan
|
* This formula is from the XLS-66 spec, section 3.2.4.2 (Total Loan
|
||||||
@@ -111,7 +191,7 @@ template <AssetType A>
|
|||||||
Number
|
Number
|
||||||
loanTotalInterestOutstanding(
|
loanTotalInterestOutstanding(
|
||||||
A asset,
|
A asset,
|
||||||
Number const& originalPrincipal,
|
std::int32_t scale,
|
||||||
Number const& principalOutstanding,
|
Number const& principalOutstanding,
|
||||||
TenthBips32 interestRate,
|
TenthBips32 interestRate,
|
||||||
std::uint32_t paymentInterval,
|
std::uint32_t paymentInterval,
|
||||||
@@ -125,94 +205,47 @@ loanTotalInterestOutstanding(
|
|||||||
principalOutstanding,
|
principalOutstanding,
|
||||||
loanTotalValueOutstanding(
|
loanTotalValueOutstanding(
|
||||||
asset,
|
asset,
|
||||||
originalPrincipal,
|
scale,
|
||||||
principalOutstanding,
|
principalOutstanding,
|
||||||
interestRate,
|
interestRate,
|
||||||
paymentInterval,
|
paymentInterval,
|
||||||
paymentsRemaining));
|
paymentsRemaining));
|
||||||
}
|
}
|
||||||
|
|
||||||
Number
|
|
||||||
loanLatePaymentInterest(
|
|
||||||
Number principalOutstanding,
|
|
||||||
TenthBips32 lateInterestRate,
|
|
||||||
NetClock::time_point parentCloseTime,
|
|
||||||
std::uint32_t startDate,
|
|
||||||
std::uint32_t prevPaymentDate);
|
|
||||||
|
|
||||||
Number
|
|
||||||
loanAccruedInterest(
|
|
||||||
Number principalOutstanding,
|
|
||||||
Number periodicRate,
|
|
||||||
NetClock::time_point parentCloseTime,
|
|
||||||
std::uint32_t startDate,
|
|
||||||
std::uint32_t prevPaymentDate,
|
|
||||||
std::uint32_t paymentInterval);
|
|
||||||
|
|
||||||
inline Number
|
|
||||||
minusManagementFee(Number value, TenthBips32 managementFeeRate)
|
|
||||||
{
|
|
||||||
return tenthBipsOfValue(value, tenthBipsPerUnity - managementFeeRate);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
template <AssetType A>
|
template <AssetType A>
|
||||||
Number
|
Number
|
||||||
valueMinusManagementFee(
|
loanInterestOutstandingMinusFee(
|
||||||
A const& asset,
|
A const& asset,
|
||||||
Number const& value,
|
Number const& totalInterestOutstanding,
|
||||||
TenthBips32 managementFeeRate,
|
TenthBips32 managementFeeRate,
|
||||||
Number const& originalPrincipal)
|
std::int32_t scale)
|
||||||
{
|
{
|
||||||
return roundToAsset(
|
return valueMinusManagementFee(
|
||||||
asset,
|
asset, totalInterestOutstanding, managementFeeRate, scale);
|
||||||
detail::minusManagementFee(value, managementFeeRate),
|
|
||||||
originalPrincipal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <AssetType A>
|
template <AssetType A>
|
||||||
Number
|
Number
|
||||||
loanInterestOutstandingMinusFee(
|
loanInterestOutstandingMinusFee(
|
||||||
A const& asset,
|
A const& asset,
|
||||||
Number const& originalPrincipal,
|
std::int32_t scale,
|
||||||
Number const& principalOutstanding,
|
Number const& principalOutstanding,
|
||||||
TenthBips32 interestRate,
|
TenthBips32 interestRate,
|
||||||
std::uint32_t paymentInterval,
|
std::uint32_t paymentInterval,
|
||||||
std::uint32_t paymentsRemaining,
|
std::uint32_t paymentsRemaining,
|
||||||
TenthBips32 managementFeeRate)
|
TenthBips32 managementFeeRate)
|
||||||
{
|
{
|
||||||
return valueMinusManagementFee(
|
return loanInterestOutstandingMinusFee(
|
||||||
asset,
|
asset,
|
||||||
detail::loanTotalInterestOutstanding(
|
loanTotalInterestOutstanding(
|
||||||
asset,
|
asset,
|
||||||
originalPrincipal,
|
scale,
|
||||||
principalOutstanding,
|
principalOutstanding,
|
||||||
interestRate,
|
interestRate,
|
||||||
paymentInterval,
|
paymentInterval,
|
||||||
paymentsRemaining),
|
paymentsRemaining),
|
||||||
managementFeeRate,
|
managementFeeRate,
|
||||||
originalPrincipal);
|
scale);
|
||||||
}
|
|
||||||
|
|
||||||
template <AssetType A>
|
|
||||||
Number
|
|
||||||
loanPeriodicPayment(
|
|
||||||
A const& asset,
|
|
||||||
Number const& principalOutstanding,
|
|
||||||
TenthBips32 interestRate,
|
|
||||||
std::uint32_t paymentInterval,
|
|
||||||
std::uint32_t paymentsRemaining,
|
|
||||||
Number const& originalPrincipal)
|
|
||||||
{
|
|
||||||
return roundToAsset(
|
|
||||||
asset,
|
|
||||||
detail::loanPeriodicPayment(
|
|
||||||
principalOutstanding,
|
|
||||||
interestRate,
|
|
||||||
paymentInterval,
|
|
||||||
paymentsRemaining),
|
|
||||||
originalPrincipal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <AssetType A>
|
template <AssetType A>
|
||||||
@@ -224,7 +257,7 @@ loanLatePaymentInterest(
|
|||||||
NetClock::time_point parentCloseTime,
|
NetClock::time_point parentCloseTime,
|
||||||
std::uint32_t startDate,
|
std::uint32_t startDate,
|
||||||
std::uint32_t prevPaymentDate,
|
std::uint32_t prevPaymentDate,
|
||||||
Number const& originalPrincipal)
|
Number const& scale)
|
||||||
{
|
{
|
||||||
return roundToAsset(
|
return roundToAsset(
|
||||||
asset,
|
asset,
|
||||||
@@ -234,7 +267,15 @@ loanLatePaymentInterest(
|
|||||||
parentCloseTime,
|
parentCloseTime,
|
||||||
startDate,
|
startDate,
|
||||||
prevPaymentDate),
|
prevPaymentDate),
|
||||||
originalPrincipal);
|
scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <AssetType A>
|
||||||
|
bool
|
||||||
|
rounded(A const& asset, Number const& value, std::int32_t scale)
|
||||||
|
{
|
||||||
|
return roundToAsset(asset, value, scale, Number::downward) == value &&
|
||||||
|
roundToAsset(asset, value, scale, Number::upward) == value;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PaymentParts
|
struct PaymentParts
|
||||||
@@ -246,9 +287,10 @@ struct PaymentParts
|
|||||||
|
|
||||||
template <AssetType A>
|
template <AssetType A>
|
||||||
PaymentParts
|
PaymentParts
|
||||||
computePeriodicPaymentParts(
|
computePaymentParts(
|
||||||
A const& asset,
|
A const& asset,
|
||||||
Number const& originalPrincipal,
|
std::int32_t scale,
|
||||||
|
Number const& totalValueOutstanding,
|
||||||
Number const& principalOutstanding,
|
Number const& principalOutstanding,
|
||||||
Number const& periodicPaymentAmount,
|
Number const& periodicPaymentAmount,
|
||||||
Number const& serviceFee,
|
Number const& serviceFee,
|
||||||
@@ -259,16 +301,17 @@ computePeriodicPaymentParts(
|
|||||||
* This function is derived from the XLS-66 spec, section 3.2.4.1.1 (Regular
|
* This function is derived from the XLS-66 spec, section 3.2.4.1.1 (Regular
|
||||||
* Payment)
|
* Payment)
|
||||||
*/
|
*/
|
||||||
Number const roundedFee =
|
XRPL_ASSERT_PARTS(
|
||||||
roundToAsset(asset, serviceFee, originalPrincipal);
|
rounded(asset, totalValueOutstanding, scale) &&
|
||||||
if (paymentRemaining == 1)
|
rounded(asset, principalOutstanding, scale) &&
|
||||||
|
rounded(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.
|
// If there's only one payment left, we need to pay off the principal.
|
||||||
Number const interest = roundToAsset(
|
Number const interest = totalValueOutstanding - principalOutstanding;
|
||||||
asset,
|
|
||||||
periodicPaymentAmount - principalOutstanding,
|
|
||||||
originalPrincipal,
|
|
||||||
Number::upward);
|
|
||||||
return {
|
return {
|
||||||
.interest = interest,
|
.interest = interest,
|
||||||
.principal = principalOutstanding,
|
.principal = principalOutstanding,
|
||||||
@@ -284,10 +327,7 @@ computePeriodicPaymentParts(
|
|||||||
* Because those values deal with funds, they need to be rounded.
|
* Because those values deal with funds, they need to be rounded.
|
||||||
*/
|
*/
|
||||||
Number const interest = roundToAsset(
|
Number const interest = roundToAsset(
|
||||||
asset,
|
asset, principalOutstanding * periodicRate, scale, Number::upward);
|
||||||
principalOutstanding * periodicRate,
|
|
||||||
originalPrincipal,
|
|
||||||
Number::upward);
|
|
||||||
XRPL_ASSERT(
|
XRPL_ASSERT(
|
||||||
interest >= 0,
|
interest >= 0,
|
||||||
"ripple::detail::computePeriodicPayment : valid interest");
|
"ripple::detail::computePeriodicPayment : valid interest");
|
||||||
@@ -296,8 +336,8 @@ computePeriodicPaymentParts(
|
|||||||
// payment amount, ensuring that some principal is paid regardless of any
|
// payment amount, ensuring that some principal is paid regardless of any
|
||||||
// other results.
|
// other results.
|
||||||
auto const roundedPayment = [&]() {
|
auto const roundedPayment = [&]() {
|
||||||
auto roundedPayment = roundToAsset(
|
auto roundedPayment =
|
||||||
asset, periodicPaymentAmount, originalPrincipal, Number::upward);
|
roundToAsset(asset, periodicPaymentAmount, scale, Number::upward);
|
||||||
if (roundedPayment > interest)
|
if (roundedPayment > interest)
|
||||||
return roundedPayment;
|
return roundedPayment;
|
||||||
auto newPayment = roundedPayment;
|
auto newPayment = roundedPayment;
|
||||||
@@ -310,21 +350,21 @@ computePeriodicPaymentParts(
|
|||||||
{
|
{
|
||||||
// Non-integral types: IOU. Add "dust" that will not be lost in
|
// Non-integral types: IOU. Add "dust" that will not be lost in
|
||||||
// rounding.
|
// rounding.
|
||||||
auto const epsilon = Number{1, originalPrincipal.exponent() - 14};
|
auto const epsilon = Number{1, scale - 14};
|
||||||
newPayment += epsilon;
|
newPayment += epsilon;
|
||||||
}
|
}
|
||||||
roundedPayment = roundToAsset(asset, newPayment, originalPrincipal);
|
roundedPayment = roundToAsset(asset, newPayment, scale);
|
||||||
XRPL_ASSERT_PARTS(
|
XRPL_ASSERT_PARTS(
|
||||||
roundedPayment == newPayment,
|
roundedPayment == newPayment,
|
||||||
"ripple::computePeriodicPaymentParts",
|
"ripple::computePaymentParts",
|
||||||
"epsilon preserved in rounding");
|
"epsilon preserved in rounding");
|
||||||
return roundedPayment;
|
return roundedPayment;
|
||||||
}();
|
}();
|
||||||
Number const principal =
|
Number const principal =
|
||||||
roundToAsset(asset, roundedPayment - interest, originalPrincipal);
|
roundToAsset(asset, roundedPayment - interest, scale);
|
||||||
XRPL_ASSERT_PARTS(
|
XRPL_ASSERT_PARTS(
|
||||||
principal > 0 && principal <= principalOutstanding,
|
principal > 0 && principal <= principalOutstanding,
|
||||||
"ripple::computePeriodicPaymentParts",
|
"ripple::computePaymentParts",
|
||||||
"valid principal");
|
"valid principal");
|
||||||
|
|
||||||
return {.interest = interest, .principal = principal, .fee = roundedFee};
|
return {.interest = interest, .principal = principal, .fee = roundedFee};
|
||||||
@@ -375,7 +415,7 @@ handleLatePayment(
|
|||||||
std::uint32_t const startDate,
|
std::uint32_t const startDate,
|
||||||
std::uint32_t const paymentInterval,
|
std::uint32_t const paymentInterval,
|
||||||
TenthBips32 const lateInterestRate,
|
TenthBips32 const lateInterestRate,
|
||||||
Number const& originalPrincipalRequested,
|
std::int32_t loanScale,
|
||||||
Number const& latePaymentFee,
|
Number const& latePaymentFee,
|
||||||
STAmount const& amount,
|
STAmount const& amount,
|
||||||
beast::Journal j)
|
beast::Journal j)
|
||||||
@@ -393,7 +433,7 @@ handleLatePayment(
|
|||||||
view.parentCloseTime(),
|
view.parentCloseTime(),
|
||||||
startDate,
|
startDate,
|
||||||
prevPaymentDateProxy,
|
prevPaymentDateProxy,
|
||||||
originalPrincipalRequested);
|
loanScale);
|
||||||
XRPL_ASSERT(
|
XRPL_ASSERT(
|
||||||
latePaymentInterest >= 0,
|
latePaymentInterest >= 0,
|
||||||
"ripple::handleLatePayment : valid late interest");
|
"ripple::handleLatePayment : valid late interest");
|
||||||
@@ -446,7 +486,7 @@ handleFullPayment(
|
|||||||
std::uint32_t const startDate,
|
std::uint32_t const startDate,
|
||||||
std::uint32_t const paymentInterval,
|
std::uint32_t const paymentInterval,
|
||||||
TenthBips32 const closeInterestRate,
|
TenthBips32 const closeInterestRate,
|
||||||
Number const& originalPrincipalRequested,
|
std::int32_t loanScale,
|
||||||
Number const& totalInterestOutstanding,
|
Number const& totalInterestOutstanding,
|
||||||
Number const& periodicRate,
|
Number const& periodicRate,
|
||||||
Number const& closePaymentFee,
|
Number const& closePaymentFee,
|
||||||
@@ -468,14 +508,14 @@ handleFullPayment(
|
|||||||
startDate,
|
startDate,
|
||||||
prevPaymentDateProxy,
|
prevPaymentDateProxy,
|
||||||
paymentInterval),
|
paymentInterval),
|
||||||
originalPrincipalRequested);
|
loanScale);
|
||||||
XRPL_ASSERT(
|
XRPL_ASSERT(
|
||||||
accruedInterest >= 0,
|
accruedInterest >= 0,
|
||||||
"ripple::handleFullPayment : valid accrued interest");
|
"ripple::handleFullPayment : valid accrued interest");
|
||||||
auto const prepaymentPenalty = roundToAsset(
|
auto const prepaymentPenalty = roundToAsset(
|
||||||
asset,
|
asset,
|
||||||
tenthBipsOfValue(principalOutstandingProxy.value(), closeInterestRate),
|
tenthBipsOfValue(principalOutstandingProxy.value(), closeInterestRate),
|
||||||
originalPrincipalRequested);
|
loanScale);
|
||||||
XRPL_ASSERT(
|
XRPL_ASSERT(
|
||||||
prepaymentPenalty >= 0,
|
prepaymentPenalty >= 0,
|
||||||
"ripple::handleFullPayment : valid prepayment "
|
"ripple::handleFullPayment : valid prepayment "
|
||||||
@@ -521,7 +561,8 @@ loanMakePayment(
|
|||||||
* This function is an implementation of the XLS-66 spec,
|
* This function is an implementation of the XLS-66 spec,
|
||||||
* section 3.2.4.3 (Transaction Pseudo-code)
|
* section 3.2.4.3 (Transaction Pseudo-code)
|
||||||
*/
|
*/
|
||||||
Number const originalPrincipalRequested = loan->at(sfPrincipalRequested);
|
std::int32_t const loanScale = loan->at(sfLoanScale);
|
||||||
|
auto totalValueOutstandingProxy = loan->at(sfTotalValueOutstanding);
|
||||||
auto principalOutstandingProxy = loan->at(sfPrincipalOutstanding);
|
auto principalOutstandingProxy = loan->at(sfPrincipalOutstanding);
|
||||||
bool const allowOverpayment = loan->isFlag(lsfLoanOverpayment);
|
bool const allowOverpayment = loan->isFlag(lsfLoanOverpayment);
|
||||||
|
|
||||||
@@ -533,8 +574,8 @@ loanMakePayment(
|
|||||||
|
|
||||||
Number const serviceFee = loan->at(sfLoanServiceFee);
|
Number const serviceFee = loan->at(sfLoanServiceFee);
|
||||||
Number const latePaymentFee = loan->at(sfLatePaymentFee);
|
Number const latePaymentFee = loan->at(sfLatePaymentFee);
|
||||||
Number const closePaymentFee = roundToAsset(
|
Number const closePaymentFee =
|
||||||
asset, loan->at(sfClosePaymentFee), originalPrincipalRequested);
|
roundToAsset(asset, loan->at(sfClosePaymentFee), loanScale);
|
||||||
TenthBips32 const overpaymentFee{loan->at(sfOverpaymentFee)};
|
TenthBips32 const overpaymentFee{loan->at(sfOverpaymentFee)};
|
||||||
|
|
||||||
std::uint32_t const paymentInterval = loan->at(sfPaymentInterval);
|
std::uint32_t const paymentInterval = loan->at(sfPaymentInterval);
|
||||||
@@ -567,26 +608,23 @@ loanMakePayment(
|
|||||||
periodicPaymentAmount > 0,
|
periodicPaymentAmount > 0,
|
||||||
"ripple::computePeriodicPayment : valid payment");
|
"ripple::computePeriodicPayment : valid payment");
|
||||||
|
|
||||||
auto const periodic = computePeriodicPaymentParts(
|
auto const periodic = computePaymentParts(
|
||||||
asset,
|
asset,
|
||||||
originalPrincipalRequested,
|
loanScale,
|
||||||
|
totalValueOutstandingProxy,
|
||||||
principalOutstandingProxy,
|
principalOutstandingProxy,
|
||||||
periodicPaymentAmount,
|
periodicPaymentAmount,
|
||||||
serviceFee,
|
serviceFee,
|
||||||
periodicRate,
|
periodicRate,
|
||||||
paymentRemainingProxy);
|
paymentRemainingProxy);
|
||||||
|
|
||||||
Number const totalValueOutstanding = detail::loanTotalValueOutstanding(
|
Number const totalValueOutstanding = loanTotalValueOutstanding(
|
||||||
asset,
|
asset, loanScale, periodicPaymentAmount, paymentRemainingProxy);
|
||||||
originalPrincipalRequested,
|
|
||||||
periodicPaymentAmount,
|
|
||||||
paymentRemainingProxy);
|
|
||||||
XRPL_ASSERT(
|
XRPL_ASSERT(
|
||||||
totalValueOutstanding > 0,
|
totalValueOutstanding > 0,
|
||||||
"ripple::loanMakePayment : valid total value");
|
"ripple::loanMakePayment : valid total value");
|
||||||
Number const totalInterestOutstanding =
|
Number const totalInterestOutstanding = loanTotalInterestOutstanding(
|
||||||
detail::loanTotalInterestOutstanding(
|
principalOutstandingProxy, totalValueOutstanding);
|
||||||
principalOutstandingProxy, totalValueOutstanding);
|
|
||||||
XRPL_ASSERT_PARTS(
|
XRPL_ASSERT_PARTS(
|
||||||
totalInterestOutstanding >= 0,
|
totalInterestOutstanding >= 0,
|
||||||
"ripple::loanMakePayment",
|
"ripple::loanMakePayment",
|
||||||
@@ -612,7 +650,7 @@ loanMakePayment(
|
|||||||
startDate,
|
startDate,
|
||||||
paymentInterval,
|
paymentInterval,
|
||||||
lateInterestRate,
|
lateInterestRate,
|
||||||
originalPrincipalRequested,
|
loanScale,
|
||||||
latePaymentFee,
|
latePaymentFee,
|
||||||
amount,
|
amount,
|
||||||
j))
|
j))
|
||||||
@@ -631,7 +669,7 @@ loanMakePayment(
|
|||||||
startDate,
|
startDate,
|
||||||
paymentInterval,
|
paymentInterval,
|
||||||
closeInterestRate,
|
closeInterestRate,
|
||||||
originalPrincipalRequested,
|
loanScale,
|
||||||
totalInterestOutstanding,
|
totalInterestOutstanding,
|
||||||
periodicRate,
|
periodicRate,
|
||||||
closePaymentFee,
|
closePaymentFee,
|
||||||
@@ -682,9 +720,10 @@ loanMakePayment(
|
|||||||
{
|
{
|
||||||
// Only do the work if we need to
|
// Only do the work if we need to
|
||||||
if (!future)
|
if (!future)
|
||||||
future = computePeriodicPaymentParts(
|
future = computePaymentParts(
|
||||||
asset,
|
asset,
|
||||||
originalPrincipalRequested,
|
loanScale,
|
||||||
|
totalValueOutstandingProxy,
|
||||||
principalOutstandingProxy,
|
principalOutstandingProxy,
|
||||||
periodicPaymentAmount,
|
periodicPaymentAmount,
|
||||||
serviceFee,
|
serviceFee,
|
||||||
@@ -707,9 +746,9 @@ loanMakePayment(
|
|||||||
|
|
||||||
Number totalFeePaid = serviceFee * fullPeriodicPayments;
|
Number totalFeePaid = serviceFee * fullPeriodicPayments;
|
||||||
|
|
||||||
Number const newInterest = detail::loanTotalInterestOutstanding(
|
Number const newInterest = loanTotalInterestOutstanding(
|
||||||
asset,
|
asset,
|
||||||
originalPrincipalRequested,
|
loanScale,
|
||||||
principalOutstandingProxy,
|
principalOutstandingProxy,
|
||||||
interestRate,
|
interestRate,
|
||||||
paymentInterval,
|
paymentInterval,
|
||||||
@@ -725,20 +764,18 @@ loanMakePayment(
|
|||||||
principalOutstandingProxy.value(),
|
principalOutstandingProxy.value(),
|
||||||
amount - (totalPrincipalPaid + totalInterestPaid + totalFeePaid));
|
amount - (totalPrincipalPaid + totalInterestPaid + totalFeePaid));
|
||||||
|
|
||||||
if (roundToAsset(asset, overpayment, originalPrincipalRequested) > 0)
|
if (roundToAsset(asset, overpayment, loanScale) > 0)
|
||||||
{
|
{
|
||||||
Number const interestPortion = roundToAsset(
|
Number const interestPortion = roundToAsset(
|
||||||
asset,
|
asset,
|
||||||
tenthBipsOfValue(overpayment, overpaymentInterestRate),
|
tenthBipsOfValue(overpayment, overpaymentInterestRate),
|
||||||
originalPrincipalRequested);
|
loanScale);
|
||||||
Number const feePortion = roundToAsset(
|
Number const feePortion = roundToAsset(
|
||||||
asset,
|
asset,
|
||||||
tenthBipsOfValue(overpayment, overpaymentFee),
|
tenthBipsOfValue(overpayment, overpaymentFee),
|
||||||
originalPrincipalRequested);
|
loanScale);
|
||||||
Number const remainder = roundToAsset(
|
Number const remainder = roundToAsset(
|
||||||
asset,
|
asset, overpayment - interestPortion - feePortion, loanScale);
|
||||||
overpayment - interestPortion - feePortion,
|
|
||||||
originalPrincipalRequested);
|
|
||||||
|
|
||||||
// Don't process an overpayment if the whole amount (or more!)
|
// Don't process an overpayment if the whole amount (or more!)
|
||||||
// gets eaten by fees
|
// gets eaten by fees
|
||||||
@@ -760,20 +797,17 @@ loanMakePayment(
|
|||||||
// Check the final results are rounded, to double-check that the
|
// Check the final results are rounded, to double-check that the
|
||||||
// intermediate steps were rounded.
|
// intermediate steps were rounded.
|
||||||
XRPL_ASSERT(
|
XRPL_ASSERT(
|
||||||
roundToAsset(asset, totalPrincipalPaid, originalPrincipalRequested) ==
|
roundToAsset(asset, totalPrincipalPaid, loanScale) ==
|
||||||
totalPrincipalPaid,
|
totalPrincipalPaid,
|
||||||
"ripple::loanMakePayment : totalPrincipalPaid rounded");
|
"ripple::loanMakePayment : totalPrincipalPaid rounded");
|
||||||
XRPL_ASSERT(
|
XRPL_ASSERT(
|
||||||
roundToAsset(asset, totalInterestPaid, originalPrincipalRequested) ==
|
roundToAsset(asset, totalInterestPaid, loanScale) == totalInterestPaid,
|
||||||
totalInterestPaid,
|
|
||||||
"ripple::loanMakePayment : totalInterestPaid rounded");
|
"ripple::loanMakePayment : totalInterestPaid rounded");
|
||||||
XRPL_ASSERT(
|
XRPL_ASSERT(
|
||||||
roundToAsset(asset, loanValueChange, originalPrincipalRequested) ==
|
roundToAsset(asset, loanValueChange, loanScale) == loanValueChange,
|
||||||
loanValueChange,
|
|
||||||
"ripple::loanMakePayment : loanValueChange rounded");
|
"ripple::loanMakePayment : loanValueChange rounded");
|
||||||
XRPL_ASSERT(
|
XRPL_ASSERT(
|
||||||
roundToAsset(asset, totalFeePaid, originalPrincipalRequested) ==
|
roundToAsset(asset, totalFeePaid, loanScale) == totalFeePaid,
|
||||||
totalFeePaid,
|
|
||||||
"ripple::loanMakePayment : totalFeePaid rounded");
|
"ripple::loanMakePayment : totalFeePaid rounded");
|
||||||
return LoanPaymentParts{
|
return LoanPaymentParts{
|
||||||
.principalPaid = totalPrincipalPaid,
|
.principalPaid = totalPrincipalPaid,
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ loanPeriodicRate(TenthBips32 interestRate, std::uint32_t paymentInterval)
|
|||||||
|
|
||||||
Number
|
Number
|
||||||
loanPeriodicPayment(
|
loanPeriodicPayment(
|
||||||
Number principalOutstanding,
|
Number const& principalOutstanding,
|
||||||
Number periodicRate,
|
Number const& periodicRate,
|
||||||
std::uint32_t paymentsRemaining)
|
std::uint32_t paymentsRemaining)
|
||||||
{
|
{
|
||||||
if (principalOutstanding == 0 || paymentsRemaining == 0)
|
if (principalOutstanding == 0 || paymentsRemaining == 0)
|
||||||
@@ -72,7 +72,7 @@ loanPeriodicPayment(
|
|||||||
|
|
||||||
Number
|
Number
|
||||||
loanPeriodicPayment(
|
loanPeriodicPayment(
|
||||||
Number principalOutstanding,
|
Number const& principalOutstanding,
|
||||||
TenthBips32 interestRate,
|
TenthBips32 interestRate,
|
||||||
std::uint32_t paymentInterval,
|
std::uint32_t paymentInterval,
|
||||||
std::uint32_t paymentsRemaining)
|
std::uint32_t paymentsRemaining)
|
||||||
@@ -91,7 +91,7 @@ loanPeriodicPayment(
|
|||||||
|
|
||||||
Number
|
Number
|
||||||
loanLatePaymentInterest(
|
loanLatePaymentInterest(
|
||||||
Number principalOutstanding,
|
Number const& principalOutstanding,
|
||||||
TenthBips32 lateInterestRate,
|
TenthBips32 lateInterestRate,
|
||||||
NetClock::time_point parentCloseTime,
|
NetClock::time_point parentCloseTime,
|
||||||
std::uint32_t startDate,
|
std::uint32_t startDate,
|
||||||
@@ -114,8 +114,8 @@ loanLatePaymentInterest(
|
|||||||
|
|
||||||
Number
|
Number
|
||||||
loanAccruedInterest(
|
loanAccruedInterest(
|
||||||
Number principalOutstanding,
|
Number const& principalOutstanding,
|
||||||
Number periodicRate,
|
Number const& periodicRate,
|
||||||
NetClock::time_point parentCloseTime,
|
NetClock::time_point parentCloseTime,
|
||||||
std::uint32_t startDate,
|
std::uint32_t startDate,
|
||||||
std::uint32_t prevPaymentDate,
|
std::uint32_t prevPaymentDate,
|
||||||
|
|||||||
@@ -2203,7 +2203,7 @@ NoModifiedUnmodifiableFields::finalize(
|
|||||||
fieldChanged(before, after, sfStartDate) ||
|
fieldChanged(before, after, sfStartDate) ||
|
||||||
fieldChanged(before, after, sfPaymentInterval) ||
|
fieldChanged(before, after, sfPaymentInterval) ||
|
||||||
fieldChanged(before, after, sfGracePeriod) ||
|
fieldChanged(before, after, sfGracePeriod) ||
|
||||||
fieldChanged(before, after, sfPrincipalRequested);
|
fieldChanged(before, after, sfLoanScale);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ LoanBrokerCoverWithdraw::preclaim(PreclaimContext const& ctx)
|
|||||||
vaultAsset,
|
vaultAsset,
|
||||||
tenthBipsOfValue(
|
tenthBipsOfValue(
|
||||||
currentDebtTotal, TenthBips32(sleBroker->at(sfCoverRateMinimum))),
|
currentDebtTotal, TenthBips32(sleBroker->at(sfCoverRateMinimum))),
|
||||||
currentDebtTotal);
|
currentDebtTotal.exponent());
|
||||||
if (coverAvail < amount)
|
if (coverAvail < amount)
|
||||||
return tecINSUFFICIENT_FUNDS;
|
return tecINSUFFICIENT_FUNDS;
|
||||||
if ((coverAvail - amount) < minimumCover)
|
if ((coverAvail - amount) < minimumCover)
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ LoanManage::defaultLoan(
|
|||||||
{
|
{
|
||||||
// Calculate the amount of the Default that First-Loss Capital covers:
|
// Calculate the amount of the Default that First-Loss Capital covers:
|
||||||
|
|
||||||
Number const originalPrincipalRequested = loanSle->at(sfPrincipalRequested);
|
std::int32_t const loanScale = loanSle->at(sfLoanScale);
|
||||||
TenthBips32 const managementFeeRate{brokerSle->at(sfManagementFeeRate)};
|
TenthBips32 const managementFeeRate{brokerSle->at(sfManagementFeeRate)};
|
||||||
auto brokerDebtTotalProxy = brokerSle->at(sfDebtTotal);
|
auto brokerDebtTotalProxy = brokerSle->at(sfDebtTotal);
|
||||||
auto const totalDefaultAmount = principalOutstanding + interestOutstanding;
|
auto const totalDefaultAmount = principalOutstanding + interestOutstanding;
|
||||||
@@ -162,7 +162,7 @@ LoanManage::defaultLoan(
|
|||||||
brokerDebtTotalProxy.value(), coverRateMinimum),
|
brokerDebtTotalProxy.value(), coverRateMinimum),
|
||||||
coverRateLiquidation),
|
coverRateLiquidation),
|
||||||
totalDefaultAmount),
|
totalDefaultAmount),
|
||||||
originalPrincipalRequested);
|
loanScale);
|
||||||
|
|
||||||
auto const vaultDefaultAmount = totalDefaultAmount - defaultCovered;
|
auto const vaultDefaultAmount = totalDefaultAmount - defaultCovered;
|
||||||
|
|
||||||
@@ -380,7 +380,7 @@ LoanManage::doApply()
|
|||||||
auto const vaultAsset = vaultSle->at(sfAsset);
|
auto const vaultAsset = vaultSle->at(sfAsset);
|
||||||
|
|
||||||
TenthBips32 const interestRate{loanSle->at(sfInterestRate)};
|
TenthBips32 const interestRate{loanSle->at(sfInterestRate)};
|
||||||
Number const originalPrincipalRequested = loanSle->at(sfPrincipalRequested);
|
std::int32_t const loanScale = loanSle->at(sfLoanScale);
|
||||||
auto const principalOutstanding = loanSle->at(sfPrincipalOutstanding);
|
auto const principalOutstanding = loanSle->at(sfPrincipalOutstanding);
|
||||||
|
|
||||||
TenthBips32 const managementFeeRate{brokerSle->at(sfManagementFeeRate)};
|
TenthBips32 const managementFeeRate{brokerSle->at(sfManagementFeeRate)};
|
||||||
@@ -388,7 +388,7 @@ LoanManage::doApply()
|
|||||||
auto const paymentsRemaining = loanSle->at(sfPaymentRemaining);
|
auto const paymentsRemaining = loanSle->at(sfPaymentRemaining);
|
||||||
auto const interestOutstanding = loanInterestOutstandingMinusFee(
|
auto const interestOutstanding = loanInterestOutstandingMinusFee(
|
||||||
vaultAsset,
|
vaultAsset,
|
||||||
originalPrincipalRequested,
|
loanScale,
|
||||||
principalOutstanding.value(),
|
principalOutstanding.value(),
|
||||||
interestRate,
|
interestRate,
|
||||||
paymentInterval,
|
paymentInterval,
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ LoanPay::doApply()
|
|||||||
|
|
||||||
//------------------------------------------------------
|
//------------------------------------------------------
|
||||||
// Loan object state changes
|
// Loan object state changes
|
||||||
Number const originalPrincipalRequested = loanSle->at(sfPrincipalRequested);
|
std::int32_t const loanScale = loanSle->at(sfLoanScale);
|
||||||
|
|
||||||
// Unimpair the loan if it was impaired. Do this before the payment is
|
// Unimpair the loan if it was impaired. Do this before the payment is
|
||||||
// attempted, so the original values can be used. If the payment fails, this
|
// attempted, so the original values can be used. If the payment fails, this
|
||||||
@@ -163,7 +163,7 @@ LoanPay::doApply()
|
|||||||
|
|
||||||
auto const interestOutstanding = loanInterestOutstandingMinusFee(
|
auto const interestOutstanding = loanInterestOutstandingMinusFee(
|
||||||
asset,
|
asset,
|
||||||
originalPrincipalRequested,
|
loanScale,
|
||||||
principalOutstanding.value(),
|
principalOutstanding.value(),
|
||||||
interestRate,
|
interestRate,
|
||||||
paymentInterval,
|
paymentInterval,
|
||||||
@@ -219,7 +219,7 @@ LoanPay::doApply()
|
|||||||
auto const managementFee = roundToAsset(
|
auto const managementFee = roundToAsset(
|
||||||
asset,
|
asset,
|
||||||
tenthBipsOfValue(paymentParts->interestPaid, managementFeeRate),
|
tenthBipsOfValue(paymentParts->interestPaid, managementFeeRate),
|
||||||
originalPrincipalRequested);
|
loanScale);
|
||||||
|
|
||||||
auto const totalPaidToVault = paymentParts->principalPaid +
|
auto const totalPaidToVault = paymentParts->principalPaid +
|
||||||
paymentParts->interestPaid - managementFee;
|
paymentParts->interestPaid - managementFee;
|
||||||
@@ -241,7 +241,7 @@ LoanPay::doApply()
|
|||||||
bool const sufficientCover = coverAvailableField >=
|
bool const sufficientCover = coverAvailableField >=
|
||||||
roundToAsset(asset,
|
roundToAsset(asset,
|
||||||
tenthBipsOfValue(debtTotalField.value(), coverRateMinimum),
|
tenthBipsOfValue(debtTotalField.value(), coverRateMinimum),
|
||||||
originalPrincipalRequested);
|
loanScale);
|
||||||
if (!sufficientCover)
|
if (!sufficientCover)
|
||||||
{
|
{
|
||||||
// Add the fee to First Loss Cover Pool
|
// Add the fee to First Loss Cover Pool
|
||||||
@@ -253,15 +253,11 @@ LoanPay::doApply()
|
|||||||
// Decrease LoanBroker Debt by the amount paid, add the Loan value change,
|
// Decrease LoanBroker Debt by the amount paid, add the Loan value change,
|
||||||
// and subtract the change in the management fee
|
// and subtract the change in the management fee
|
||||||
auto const vaultValueChange = valueMinusManagementFee(
|
auto const vaultValueChange = valueMinusManagementFee(
|
||||||
asset,
|
asset, paymentParts->valueChange, managementFeeRate, loanScale);
|
||||||
paymentParts->valueChange,
|
|
||||||
managementFeeRate,
|
|
||||||
originalPrincipalRequested);
|
|
||||||
// debtDecrease may be negative, increasing the debt
|
// debtDecrease may be negative, increasing the debt
|
||||||
auto const debtDecrease = totalPaidToVault - vaultValueChange;
|
auto const debtDecrease = totalPaidToVault - vaultValueChange;
|
||||||
XRPL_ASSERT_PARTS(
|
XRPL_ASSERT_PARTS(
|
||||||
roundToAsset(asset, debtDecrease, originalPrincipalRequested) ==
|
roundToAsset(asset, debtDecrease, loanScale) == debtDecrease,
|
||||||
debtDecrease,
|
|
||||||
"ripple::LoanPay::doApply",
|
"ripple::LoanPay::doApply",
|
||||||
"debtDecrease rounding good");
|
"debtDecrease rounding good");
|
||||||
if (debtDecrease >= debtTotalField)
|
if (debtDecrease >= debtTotalField)
|
||||||
|
|||||||
@@ -246,48 +246,6 @@ LoanSet::preclaim(PreclaimContext const& ctx)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto const principalRequested = tx[sfPrincipalRequested];
|
|
||||||
if (auto const assetsAvailable = vault->at(sfAssetsAvailable);
|
|
||||||
assetsAvailable < principalRequested)
|
|
||||||
{
|
|
||||||
JLOG(ctx.j.warn())
|
|
||||||
<< "Insufficient assets available in the Vault to fund the loan.";
|
|
||||||
return tecINSUFFICIENT_FUNDS;
|
|
||||||
}
|
|
||||||
|
|
||||||
TenthBips32 const interestRate{tx[~sfInterestRate].value_or(0)};
|
|
||||||
auto const paymentInterval =
|
|
||||||
tx[~sfPaymentInterval].value_or(defaultPaymentInterval);
|
|
||||||
auto const paymentTotal = tx[~sfPaymentTotal].value_or(defaultPaymentTotal);
|
|
||||||
TenthBips32 const managementFeeRate{brokerSle->at(sfManagementFeeRate)};
|
|
||||||
|
|
||||||
auto const totalInterest = loanInterestOutstandingMinusFee(
|
|
||||||
asset,
|
|
||||||
principalRequested,
|
|
||||||
principalRequested,
|
|
||||||
interestRate,
|
|
||||||
paymentInterval,
|
|
||||||
paymentTotal,
|
|
||||||
managementFeeRate);
|
|
||||||
|
|
||||||
auto const newDebtTotal =
|
|
||||||
brokerSle->at(sfDebtTotal) + principalRequested + totalInterest;
|
|
||||||
if (auto const debtMaximum = brokerSle->at(sfDebtMaximum);
|
|
||||||
debtMaximum != 0 && debtMaximum < newDebtTotal)
|
|
||||||
{
|
|
||||||
JLOG(ctx.j.warn())
|
|
||||||
<< "Loan would exceed the maximum debt limit of the LoanBroker.";
|
|
||||||
return tecLIMIT_EXCEEDED;
|
|
||||||
}
|
|
||||||
TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)};
|
|
||||||
if (brokerSle->at(sfCoverAvailable) <
|
|
||||||
tenthBipsOfValue(newDebtTotal, coverRateMinimum))
|
|
||||||
{
|
|
||||||
JLOG(ctx.j.warn())
|
|
||||||
<< "Insufficient first-loss capital to cover the loan.";
|
|
||||||
return tecINSUFFICIENT_FUNDS;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,17 +285,89 @@ LoanSet::doApply()
|
|||||||
{
|
{
|
||||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
auto const principalRequested = roundToAsset(
|
auto const principalRequested = [&](Number const& requested) {
|
||||||
vaultAsset, tx[sfPrincipalRequested], tx[sfPrincipalRequested]);
|
return roundToAsset(vaultAsset, requested, requested.exponent());
|
||||||
|
}(tx[sfPrincipalRequested]);
|
||||||
|
auto const loanScale = principalRequested.exponent();
|
||||||
|
|
||||||
|
if (auto const assetsAvailable = vaultSle->at(sfAssetsAvailable);
|
||||||
|
assetsAvailable < principalRequested)
|
||||||
|
{
|
||||||
|
JLOG(j_.warn())
|
||||||
|
<< "Insufficient assets available in the Vault to fund the loan.";
|
||||||
|
return tecINSUFFICIENT_FUNDS;
|
||||||
|
}
|
||||||
|
|
||||||
TenthBips32 const interestRate{tx[~sfInterestRate].value_or(0)};
|
TenthBips32 const interestRate{tx[~sfInterestRate].value_or(0)};
|
||||||
auto const originationFee = tx[~sfLoanOriginationFee];
|
|
||||||
auto const loanAssetsAvailable =
|
auto const originationFee = roundToAsset(
|
||||||
principalRequested - originationFee.value_or(Number{});
|
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);
|
||||||
|
|
||||||
|
{
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TenthBips32 const managementFeeRate{brokerSle->at(sfManagementFeeRate)};
|
||||||
|
|
||||||
|
auto const totalInterestOwedToVault = [&]() {
|
||||||
|
auto const totalInterestOutstanding = loanTotalInterestOutstanding(
|
||||||
|
principalRequested, totalValueOutstanding);
|
||||||
|
|
||||||
|
return loanInterestOutstandingMinusFee(
|
||||||
|
vaultAsset, totalInterestOutstanding, managementFeeRate, loanScale);
|
||||||
|
}();
|
||||||
|
|
||||||
|
auto const newDebtTotal = brokerSle->at(sfDebtTotal) + principalRequested +
|
||||||
|
totalInterestOwedToVault;
|
||||||
|
if (auto const debtMaximum = brokerSle->at(sfDebtMaximum);
|
||||||
|
debtMaximum != 0 && debtMaximum < newDebtTotal)
|
||||||
|
{
|
||||||
|
JLOG(j_.warn())
|
||||||
|
<< "Loan would exceed the maximum debt limit of the LoanBroker.";
|
||||||
|
return tecLIMIT_EXCEEDED;
|
||||||
|
}
|
||||||
|
TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)};
|
||||||
|
if (brokerSle->at(sfCoverAvailable) <
|
||||||
|
tenthBipsOfValue(newDebtTotal, coverRateMinimum))
|
||||||
|
{
|
||||||
|
JLOG(j_.warn()) << "Insufficient first-loss capital to cover the loan.";
|
||||||
|
return tecINSUFFICIENT_FUNDS;
|
||||||
|
}
|
||||||
|
|
||||||
adjustOwnerCount(view, borrowerSle, 1, j_);
|
adjustOwnerCount(view, borrowerSle, 1, j_);
|
||||||
auto ownerCount = borrowerSle->at(sfOwnerCount);
|
{
|
||||||
if (mPriorBalance < view.fees().accountReserve(ownerCount))
|
auto ownerCount = borrowerSle->at(sfOwnerCount);
|
||||||
return tecINSUFFICIENT_RESERVE;
|
if (mPriorBalance < view.fees().accountReserve(ownerCount))
|
||||||
|
return tecINSUFFICIENT_RESERVE;
|
||||||
|
}
|
||||||
|
|
||||||
// Account for the origination fee using two payments
|
// Account for the origination fee using two payments
|
||||||
//
|
//
|
||||||
@@ -359,13 +389,13 @@ LoanSet::doApply()
|
|||||||
view,
|
view,
|
||||||
vaultPseudo,
|
vaultPseudo,
|
||||||
borrower,
|
borrower,
|
||||||
STAmount{vaultAsset, loanAssetsAvailable},
|
STAmount{vaultAsset, loanAssetsToBorrower},
|
||||||
j_,
|
j_,
|
||||||
WaiveTransferFee::Yes))
|
WaiveTransferFee::Yes))
|
||||||
return ter;
|
return ter;
|
||||||
// 2. Transfer originationFee, if any, from vault pseudo-account to
|
// 2. Transfer originationFee, if any, from vault pseudo-account to
|
||||||
// LoanBroker owner.
|
// LoanBroker owner.
|
||||||
if (originationFee && (*originationFee != Number{}))
|
if (originationFee != Number{})
|
||||||
{
|
{
|
||||||
// Create the holding if it doesn't already exist (necessary for MPTs).
|
// Create the holding if it doesn't already exist (necessary for MPTs).
|
||||||
// The owner may have deleted their MPT / line at some point.
|
// The owner may have deleted their MPT / line at some point.
|
||||||
@@ -383,31 +413,20 @@ LoanSet::doApply()
|
|||||||
view,
|
view,
|
||||||
vaultPseudo,
|
vaultPseudo,
|
||||||
brokerOwner,
|
brokerOwner,
|
||||||
STAmount{vaultAsset, *originationFee},
|
STAmount{vaultAsset, originationFee},
|
||||||
j_,
|
j_,
|
||||||
WaiveTransferFee::Yes))
|
WaiveTransferFee::Yes))
|
||||||
return ter;
|
return ter;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto const paymentInterval =
|
|
||||||
tx[~sfPaymentInterval].value_or(defaultPaymentInterval);
|
|
||||||
auto const paymentTotal = tx[~sfPaymentTotal].value_or(defaultPaymentTotal);
|
|
||||||
TenthBips32 const managementFeeRate{brokerSle->at(sfManagementFeeRate)};
|
|
||||||
// The portion of the loan interest that will go to the vault (total
|
// The portion of the loan interest that will go to the vault (total
|
||||||
// interest minus the management fee)
|
// interest minus the management fee)
|
||||||
auto const loanInterestToVault = loanInterestOutstandingMinusFee(
|
|
||||||
vaultAsset,
|
|
||||||
principalRequested,
|
|
||||||
principalRequested,
|
|
||||||
interestRate,
|
|
||||||
paymentInterval,
|
|
||||||
paymentTotal,
|
|
||||||
managementFeeRate);
|
|
||||||
auto const startDate = view.info().closeTime.time_since_epoch().count();
|
auto const startDate = view.info().closeTime.time_since_epoch().count();
|
||||||
auto loanSequence = brokerSle->at(sfLoanSequence);
|
auto loanSequenceProxy = brokerSle->at(sfLoanSequence);
|
||||||
|
|
||||||
// Create the loan
|
// Create the loan
|
||||||
auto loan = std::make_shared<SLE>(keylet::loan(brokerID, *loanSequence));
|
auto loan =
|
||||||
|
std::make_shared<SLE>(keylet::loan(brokerID, *loanSequenceProxy));
|
||||||
|
|
||||||
// Prevent copy/paste errors
|
// Prevent copy/paste errors
|
||||||
auto setLoanField =
|
auto setLoanField =
|
||||||
@@ -417,12 +436,11 @@ LoanSet::doApply()
|
|||||||
loan->at(field) = tx[field].value_or(defValue);
|
loan->at(field) = tx[field].value_or(defValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set required tx fields and pre-computed fields
|
// Set required and fixed tx fields
|
||||||
loan->at(sfPrincipalRequested) = principalRequested;
|
loan->at(sfLoanScale) = principalRequested.exponent();
|
||||||
loan->at(sfPrincipalOutstanding) = principalRequested;
|
|
||||||
loan->at(sfStartDate) = startDate;
|
loan->at(sfStartDate) = startDate;
|
||||||
loan->at(sfPaymentInterval) = paymentInterval;
|
loan->at(sfPaymentInterval) = paymentInterval;
|
||||||
loan->at(sfLoanSequence) = loanSequence;
|
loan->at(sfLoanSequence) = *loanSequenceProxy;
|
||||||
loan->at(sfLoanBrokerID) = brokerID;
|
loan->at(sfLoanBrokerID) = brokerID;
|
||||||
loan->at(sfBorrower) = borrower;
|
loan->at(sfBorrower) = borrower;
|
||||||
// Set all other transaction fields directly from the transaction
|
// Set all other transaction fields directly from the transaction
|
||||||
@@ -438,7 +456,9 @@ LoanSet::doApply()
|
|||||||
setLoanField(~sfCloseInterestRate);
|
setLoanField(~sfCloseInterestRate);
|
||||||
setLoanField(~sfOverpaymentInterestRate);
|
setLoanField(~sfOverpaymentInterestRate);
|
||||||
setLoanField(~sfGracePeriod, defaultGracePeriod);
|
setLoanField(~sfGracePeriod, defaultGracePeriod);
|
||||||
// Set dynamic fields to their initial values
|
// Set dynamic / computed fields to their initial values
|
||||||
|
loan->at(sfPrincipalOutstanding) = principalRequested;
|
||||||
|
loan->at(sfTotalValueOutstanding) = totalValueOutstanding;
|
||||||
loan->at(sfPreviousPaymentDate) = 0;
|
loan->at(sfPreviousPaymentDate) = 0;
|
||||||
loan->at(sfNextPaymentDueDate) = startDate + paymentInterval;
|
loan->at(sfNextPaymentDueDate) = startDate + paymentInterval;
|
||||||
loan->at(sfPaymentRemaining) = paymentTotal;
|
loan->at(sfPaymentRemaining) = paymentTotal;
|
||||||
@@ -446,7 +466,7 @@ LoanSet::doApply()
|
|||||||
|
|
||||||
// Update the balances in the vault
|
// Update the balances in the vault
|
||||||
vaultSle->at(sfAssetsAvailable) -= principalRequested;
|
vaultSle->at(sfAssetsAvailable) -= principalRequested;
|
||||||
vaultSle->at(sfAssetsTotal) += loanInterestToVault;
|
vaultSle->at(sfAssetsTotal) += totalInterestOwedToVault;
|
||||||
XRPL_ASSERT_PARTS(
|
XRPL_ASSERT_PARTS(
|
||||||
*vaultSle->at(sfAssetsAvailable) <= *vaultSle->at(sfAssetsTotal),
|
*vaultSle->at(sfAssetsAvailable) <= *vaultSle->at(sfAssetsTotal),
|
||||||
"ripple::LoanSet::doApply",
|
"ripple::LoanSet::doApply",
|
||||||
@@ -454,11 +474,11 @@ LoanSet::doApply()
|
|||||||
view.update(vaultSle);
|
view.update(vaultSle);
|
||||||
|
|
||||||
// Update the balances in the loan broker
|
// Update the balances in the loan broker
|
||||||
brokerSle->at(sfDebtTotal) += principalRequested + loanInterestToVault;
|
brokerSle->at(sfDebtTotal) += principalRequested + totalInterestOwedToVault;
|
||||||
// The broker's owner count is solely for the number of outstanding loans,
|
// 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
|
// and is distinct from the broker's pseudo-account's owner count
|
||||||
adjustOwnerCount(view, brokerSle, 1, j_);
|
adjustOwnerCount(view, brokerSle, 1, j_);
|
||||||
loanSequence += 1;
|
loanSequenceProxy += 1;
|
||||||
view.update(brokerSle);
|
view.update(brokerSle);
|
||||||
|
|
||||||
// Put the loan into the pseudo-account's directory
|
// Put the loan into the pseudo-account's directory
|
||||||
|
|||||||
Reference in New Issue
Block a user