284 auto const& tx =
ctx_.tx;
287 auto const amount = tx[sfAmount];
289 auto const loanID = tx[sfLoanID];
293 std::int32_t const loanScale = loanSle->at(sfLoanScale);
295 auto const brokerID = loanSle->at(sfLoanBrokerID);
299 auto const brokerOwner = brokerSle->at(sfOwner);
300 auto const brokerPseudoAccount = brokerSle->at(sfAccount);
301 auto const vaultID = brokerSle->at(sfVaultID);
305 auto const vaultPseudoAccount = vaultSle->at(sfAccount);
306 auto const asset = *vaultSle->at(sfAsset);
309 auto coverAvailableProxy = brokerSle->at(sfCoverAvailable);
310 TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)};
311 auto debtTotalProxy = brokerSle->at(sfDebtTotal);
323 bool const sendBrokerFeeToOwner = [&]() {
327 auto const minCover = [&]() {
328 if (
view.rules().enabled(fixCleanup3_2_0))
337 asset,
tenthBipsOfValue(debtTotalProxy.value(), coverRateMinimum), loanScale);
339 return coverAvailableProxy >= minCover && !
isDeepFrozen(
view, brokerOwner, asset) &&
343 auto const brokerPayee = sendBrokerFeeToOwner ? brokerOwner : brokerPseudoAccount;
345 if (!sendBrokerFeeToOwner)
351 JLOG(
j_.warn()) <<
"Both Loan Broker and Loan Broker pseudo-account "
352 "can not receive funds (deep frozen).";
363 if (loanSle->isFlag(lsfLoanImpaired))
367 JLOG(
j_.fatal()) <<
"Failed to unimpair loan before payment.";
374 if (tx.isFlag(tfLoanLatePayment))
376 if (tx.isFlag(tfLoanFullPayment))
378 if (tx.isFlag(tfLoanOverpayment))
383 std::expected<LoanPaymentParts, TER>
const paymentParts =
389 paymentParts.error(),
"xrpl::LoanPay::doApply",
"payment error is an error");
390 return paymentParts.error();
395 view.update(loanSle);
399 paymentParts->principalPaid >= 0,
400 "xrpl::LoanPay::doApply",
401 "valid principal paid");
404 paymentParts->interestPaid >= 0,
405 "xrpl::LoanPay::doApply",
406 "valid interest paid");
409 paymentParts->principalPaid + paymentParts->interestPaid > 0,
410 "xrpl::LoanPay::doApply",
412 XRPL_ASSERT_PARTS(paymentParts->feePaid >= 0,
"xrpl::LoanPay::doApply",
"valid fee paid");
414 if (paymentParts->principalPaid < 0 || paymentParts->interestPaid < 0 ||
415 paymentParts->feePaid < 0)
418 JLOG(
j_.fatal()) <<
"Loan payment computation returned invalid values.";
423 JLOG(
j_.debug()) <<
"Loan Pay: principal paid: " << paymentParts->principalPaid
424 <<
", interest paid: " << paymentParts->interestPaid
425 <<
", fee paid: " << paymentParts->feePaid
426 <<
", value change: " << paymentParts->valueChange;
430 view.update(brokerSle);
432 auto assetsAvailableProxy = vaultSle->at(sfAssetsAvailable);
433 auto assetsTotalProxy = vaultSle->at(sfAssetsTotal);
435 auto const totalPaidToVaultRaw = paymentParts->principalPaid + paymentParts->interestPaid;
436 auto const totalPaidToVaultRounded =
439 !asset.integral() || totalPaidToVaultRaw == totalPaidToVaultRounded,
440 "xrpl::LoanPay::doApply",
441 "rounding does nothing for integral asset");
447 auto const totalPaidToVaultForDebt = totalPaidToVaultRaw - paymentParts->valueChange;
449 auto const totalPaidToBroker = paymentParts->feePaid;
452 (totalPaidToVaultRaw + totalPaidToBroker) ==
453 (paymentParts->principalPaid + paymentParts->interestPaid + paymentParts->feePaid),
454 "xrpl::LoanPay::doApply",
461 isRounded(asset, totalPaidToVaultForDebt, loanScale),
462 "xrpl::LoanPay::doApply",
463 "totalPaidToVaultForDebt rounding good");
471 view.update(vaultSle);
473 Number const assetsAvailableBefore = *assetsAvailableProxy;
474 Number const assetsTotalBefore = *assetsTotalProxy;
486 assetsAvailableBefore == pseudoAccountBalanceBefore,
487 "xrpl::LoanPay::doApply",
488 "vault pseudo balance agrees before");
492 assetsAvailableProxy += totalPaidToVaultRounded;
493 assetsTotalProxy += paymentParts->valueChange;
496 *assetsAvailableProxy <= *assetsTotalProxy,
497 "xrpl::LoanPay::doApply",
498 "assets available must not be greater than assets outstanding");
500 JLOG(
j_.debug()) <<
"total paid to vault raw: " << totalPaidToVaultRaw
501 <<
", total paid to vault rounded: " << totalPaidToVaultRounded
502 <<
", total paid to broker: " << totalPaidToBroker
503 <<
", amount from transaction: " << amount;
507 totalPaidToVaultRounded + totalPaidToBroker <= amount,
508 "xrpl::LoanPay::doApply",
509 "amount is sufficient");
511 if (!sendBrokerFeeToOwner)
517 coverAvailableProxy += totalPaidToBroker;
525 Number const assetsAvailableAfter = *assetsAvailableProxy;
526 Number const assetsTotalAfter = *assetsTotalProxy;
529 assetsAvailableAfter <= assetsTotalAfter,
530 "xrpl::LoanPay::doApply",
531 "assets available must not be greater than assets outstanding");
532 if (assetsAvailableAfter == assetsAvailableBefore)
540 JLOG(
j_.warn()) <<
"LoanPay: Vault assets available unchanged after rounding: "
541 <<
"Before: " << assetsAvailableBefore
542 <<
", After: " << assetsAvailableAfter;
546 if (paymentParts->valueChange != beast::kZero && assetsTotalAfter == assetsTotalBefore)
555 <<
"LoanPay: Vault assets expected change, but unchanged after rounding: "
556 <<
"Before: " << assetsTotalBefore
557 <<
", After: " << assetsTotalAfter
558 <<
", ValueChange: " << paymentParts->valueChange;
562 if (paymentParts->valueChange == beast::kZero && assetsTotalAfter != assetsTotalBefore)
568 JLOG(
j_.fatal()) <<
"LoanPay: Vault assets changed unexpectedly after rounding: "
569 <<
"Before: " << assetsTotalBefore
570 <<
", After: " << assetsTotalAfter
571 <<
", ValueChange: " << paymentParts->valueChange;
575 if (assetsAvailableAfter > assetsTotalAfter)
579 JLOG(
j_.fatal()) <<
"LoanPay: Vault assets available must not be greater "
580 "than assets outstanding. Available: "
581 << assetsAvailableAfter <<
", Total: " << assetsTotalAfter;
595 auto const vaultBalanceBefore =
accountID_ == vaultPseudoAccount
605 auto const brokerBalanceBefore =
accountID_ == brokerPayee
616 if (totalPaidToVaultRounded != beast::kZero)
622 if (totalPaidToBroker != beast::kZero)
628 view, brokerPayee, brokerPayeeSle->at(sfBalance).value().xrp(), asset,
j_);
644 {{vaultPseudoAccount, totalPaidToVaultRounded}, {brokerPayee, totalPaidToBroker}},
659 assetsAvailableAfter == pseudoAccountBalanceAfter,
660 "xrpl::LoanPay::doApply",
661 "vault pseudo balance agrees after");
674 auto const vaultBalanceAfter =
accountID_ == vaultPseudoAccount
693 auto const balanceScale = [&]() {
703 for (
auto const& a : {
704 accountBalanceBefore,
713 if (a != beast::kZero)
716 if (exponents.
empty())
718 UNREACHABLE(
"xrpl::LoanPay::doApply : all zeroes");
722 auto const min = *minItr;
723 auto const max = *maxItr;
724 JLOG(
j_.trace()) <<
"Min scale: " << min <<
", max scale: " << max;
735 "xrpl::LoanPay::doApply",
736 "Number rounding ToNearest");
739 auto const accountBalanceBeforeRounded =
roundToScale(accountBalanceBefore, balanceScale);
740 auto const vaultBalanceBeforeRounded =
roundToScale(vaultBalanceBefore, balanceScale);
741 auto const brokerBalanceBeforeRounded =
roundToScale(brokerBalanceBefore, balanceScale);
743 auto const totalBalanceBefore = accountBalanceBefore + vaultBalanceBefore + brokerBalanceBefore;
744 auto const totalBalanceBeforeRounded =
roundToScale(totalBalanceBefore, balanceScale);
746 JLOG(
j_.trace()) <<
"Before: "
747 <<
"account " <<
Number(accountBalanceBeforeRounded) <<
" ("
748 <<
Number(accountBalanceBefore) <<
")"
749 <<
", vault " <<
Number(vaultBalanceBeforeRounded) <<
" ("
750 <<
Number(vaultBalanceBefore) <<
")"
751 <<
", broker " <<
Number(brokerBalanceBeforeRounded) <<
" ("
752 <<
Number(brokerBalanceBefore) <<
")"
753 <<
", total " <<
Number(totalBalanceBeforeRounded) <<
" ("
754 <<
Number(totalBalanceBefore) <<
")";
756 auto const accountBalanceAfterRounded =
roundToScale(accountBalanceAfter, balanceScale);
757 auto const vaultBalanceAfterRounded =
roundToScale(vaultBalanceAfter, balanceScale);
758 auto const brokerBalanceAfterRounded =
roundToScale(brokerBalanceAfter, balanceScale);
760 auto const totalBalanceAfter = accountBalanceAfter + vaultBalanceAfter + brokerBalanceAfter;
761 auto const totalBalanceAfterRounded =
roundToScale(totalBalanceAfter, balanceScale);
763 JLOG(
j_.trace()) <<
"After: "
764 <<
"account " <<
Number(accountBalanceAfterRounded) <<
" ("
765 <<
Number(accountBalanceAfter) <<
")"
766 <<
", vault " <<
Number(vaultBalanceAfterRounded) <<
" ("
767 <<
Number(vaultBalanceAfter) <<
")"
768 <<
", broker " <<
Number(brokerBalanceAfterRounded) <<
" ("
769 <<
Number(brokerBalanceAfter) <<
")"
770 <<
", total " <<
Number(totalBalanceAfterRounded) <<
" ("
771 <<
Number(totalBalanceAfter) <<
")";
773 auto const accountBalanceChange = accountBalanceAfter - accountBalanceBefore;
774 auto const vaultBalanceChange = vaultBalanceAfter - vaultBalanceBefore;
775 auto const brokerBalanceChange = brokerBalanceAfter - brokerBalanceBefore;
777 auto const totalBalanceChange = accountBalanceChange + vaultBalanceChange + brokerBalanceChange;
778 auto const totalBalanceChangeRounded =
roundToScale(totalBalanceChange, balanceScale);
780 JLOG(
j_.trace()) <<
"Changes: "
781 <<
"account " <<
to_string(accountBalanceChange)
782 <<
", vault " <<
to_string(vaultBalanceChange)
783 <<
", broker " <<
to_string(brokerBalanceChange)
784 <<
", total " <<
to_string(totalBalanceChangeRounded) <<
" ("
785 <<
Number(totalBalanceChange) <<
")";
787 bool const goodRounding = totalBalanceBeforeRounded == totalBalanceAfterRounded ||
788 totalBalanceChangeRounded == beast::kZero;
789 if (totalBalanceBeforeRounded != totalBalanceAfterRounded)
791 JLOG((goodRounding ?
j_.debug() :
j_.warn()))
792 <<
"Total rounded balances don't match"
793 << (totalBalanceChangeRounded == beast::kZero ?
", but total changes do" :
"");
795 if (totalBalanceChangeRounded != beast::kZero)
797 JLOG((goodRounding ?
j_.debug() :
j_.warn()))
798 <<
"Total balance changes don't match"
799 << (totalBalanceBeforeRounded == totalBalanceAfterRounded ?
", but total balances do"
806 goodRounding,
"xrpl::LoanPay::doApply",
"funds are conserved (with rounding)");
809 accountBalanceAfter < accountBalanceBefore ||
accountID_ == asset.getIssuer(),
810 "xrpl::LoanPay::doApply",
811 "account balance decreased");
813 vaultBalanceAfter >= beast::kZero && brokerBalanceAfter >= beast::kZero,
814 "xrpl::LoanPay::doApply",
815 "positive vault and broker balances");
817 vaultBalanceAfter >= vaultBalanceBefore,
818 "xrpl::LoanPay::doApply",
819 "vault balance did not decrease");
821 brokerBalanceAfter >= brokerBalanceBefore,
822 "xrpl::LoanPay::doApply",
823 "broker balance did not decrease");
825 vaultBalanceAfter > vaultBalanceBefore || brokerBalanceAfter > brokerBalanceBefore,
826 "xrpl::LoanPay::doApply",
827 "vault and/or broker balance increased");