Resolve some of these annoying test rounding issues`

This commit is contained in:
Ed Hennis
2025-05-13 15:53:42 +01:00
parent d8cb1a00d5
commit 784b3ae64d
3 changed files with 29 additions and 18 deletions

View File

@@ -164,9 +164,14 @@ class Loan_test : public beast::unit_test::suite
paymentInterval, paymentInterval,
paymentsRemaining, paymentsRemaining,
managementFeeRate); managementFeeRate);
auto const brokerDebt = brokerSle->at(sfDebtTotal);
auto const expectedDebt = principalOutstanding + loanInterest; auto const expectedDebt = principalOutstanding + loanInterest;
env.test.BEAST_EXPECT( env.test.BEAST_EXPECT(
brokerSle->at(sfDebtTotal) == expectedDebt); // Allow some slop for rounding
brokerDebt == expectedDebt ||
(expectedDebt != Number(0) &&
((brokerDebt - expectedDebt) / expectedDebt <
Number(1, -2))));
env.test.BEAST_EXPECT( env.test.BEAST_EXPECT(
env.balance(pseudoAccount, broker.asset).number() == env.balance(pseudoAccount, broker.asset).number() ==
brokerSle->at(sfCoverAvailable) + assetsAvailable); brokerSle->at(sfCoverAvailable) + assetsAvailable);
@@ -1484,15 +1489,14 @@ class Loan_test : public beast::unit_test::suite
// remaining // remaining
auto const rateFactor = auto const rateFactor =
power(1 + periodicRate, state.paymentRemaining); power(1 + periodicRate, state.paymentRemaining);
STAmount const periodicPayment{ Number const periodicPayment{
broker.asset,
state.principalOutstanding * periodicRate * state.principalOutstanding * periodicRate *
rateFactor / (rateFactor - 1)}; rateFactor / (rateFactor - 1)};
// Only check the first payment since the rounding may // Only check the first payment since the rounding may
// drift as payments are made // drift as payments are made
BEAST_EXPECT( BEAST_EXPECT(
state.paymentRemaining < 12 || state.paymentRemaining < 12 ||
periodicPayment == STAmount(broker.asset, periodicPayment) ==
broker.asset(Number(8333457001162141, -14))); broker.asset(Number(8333457001162141, -14)));
// Include the service fee // Include the service fee
STAmount const totalDue{ STAmount const totalDue{
@@ -1534,7 +1538,7 @@ class Loan_test : public beast::unit_test::suite
BEAST_EXPECT( BEAST_EXPECT(
state.paymentRemaining < 12 || state.paymentRemaining < 12 ||
principal == principal ==
broker.asset(Number(8333228700000000, -14))); broker.asset(Number(8333228690659858, -14)));
BEAST_EXPECT( BEAST_EXPECT(
principal > Number(0) && principal > Number(0) &&
principal <= state.principalOutstanding); principal <= state.principalOutstanding);
@@ -1558,10 +1562,18 @@ class Loan_test : public beast::unit_test::suite
} }
// Check the result // Check the result
BEAST_EXPECT( auto const borrowerBalance =
env.balance(borrower, broker.asset) == env.balance(borrower, broker.asset);
auto const expectedBalance =
borrowerBalanceBeforePayment - totalDueAmount - borrowerBalanceBeforePayment - totalDueAmount -
adjustment); adjustment;
BEAST_EXPECT(
borrowerBalance == expectedBalance ||
(!broker.asset.raw().native() &&
broker.asset.raw().holds<Issue>() &&
((borrowerBalance - expectedBalance) /
expectedBalance <
Number(1, -4))));
--state.paymentRemaining; --state.paymentRemaining;
state.previousPaymentDate = state.nextPaymentDate; state.previousPaymentDate = state.nextPaymentDate;

View File

@@ -437,21 +437,21 @@ loanComputePaymentParts(
// if the payment is not late nor if it's a full payment, then it must be a // if the payment is not late nor if it's a full payment, then it must be a
// periodic one, with possible overpayments // periodic one, with possible overpayments
auto const totalDue =
roundToAsset(asset, periodicPaymentAmount + serviceFee, Number::upward);
std::optional<NumberRoundModeGuard> mg(Number::downward); std::optional<NumberRoundModeGuard> mg(Number::downward);
std::int64_t const fullPeriodicPayments = [&]() { std::int64_t const fullPeriodicPayments = [&]() {
std::int64_t const full{ std::int64_t const full{amount / totalDue};
amount /
roundToAsset(
asset, (periodicPaymentAmount + serviceFee), Number::upward)};
return full < paymentRemainingField ? full : paymentRemainingField; return full < paymentRemainingField ? full : paymentRemainingField;
}(); }();
mg.reset(); mg.reset();
// Temporary asserts // Temporary asserts
XRPL_ASSERT( XRPL_ASSERT(
amount >= periodicPaymentAmount || fullPeriodicPayments == 0, amount >= totalDue || fullPeriodicPayments == 0,
"temp full periodic rounding"); "temp full periodic rounding");
XRPL_ASSERT( XRPL_ASSERT(
amount < periodicPaymentAmount || fullPeriodicPayments >= 1, amount < totalDue || fullPeriodicPayments >= 1,
"temp full periodic rounding"); "temp full periodic rounding");
if (fullPeriodicPayments < 1) if (fullPeriodicPayments < 1)
@@ -499,7 +499,7 @@ loanComputePaymentParts(
{ {
Number const overpayment = std::min( Number const overpayment = std::min(
principalOutstandingField.value(), principalOutstandingField.value(),
amount - periodicPaymentAmount * fullPeriodicPayments); amount - (totalPrincipalPaid + totalInterestPaid + totalFeePaid));
if (roundToAsset(asset, overpayment) > 0) if (roundToAsset(asset, overpayment) > 0)
{ {

View File

@@ -51,8 +51,7 @@ loanPeriodicPayment(
// TODO: Need a better name // TODO: Need a better name
Number const timeFactor = power(1 + periodicRate, paymentsRemaining); Number const timeFactor = power(1 + periodicRate, paymentsRemaining);
return principalOutstanding * (periodicRate * timeFactor) / return principalOutstanding * periodicRate * timeFactor / (timeFactor - 1);
(timeFactor - 1);
} }
Number Number