334 auto const& tx =
ctx_.tx;
337 auto const amount = tx[sfAmount];
339 auto const loanID = tx[sfLoanID];
343 std::int32_t const loanScale = loanSle->at(sfLoanScale);
345 auto const brokerID = loanSle->at(sfLoanBrokerID);
349 auto const brokerOwner = brokerSle->at(sfOwner);
350 auto const brokerPseudoAccount = brokerSle->at(sfAccount);
351 auto const vaultID = brokerSle->at(sfVaultID);
355 auto const vaultPseudoAccount = vaultSle->at(sfAccount);
356 auto const asset = *vaultSle->at(sfAsset);
359 auto coverAvailableProxy = brokerSle->at(sfCoverAvailable);
360 TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)};
361 auto debtTotalProxy = brokerSle->at(sfDebtTotal);
373 bool const sendBrokerFeeToOwner = [&]() {
377 auto const minCover = [&]() {
378 if (
view.rules().enabled(fixCleanup3_2_0))
387 asset,
tenthBipsOfValue(debtTotalProxy.value(), coverRateMinimum), loanScale);
389 return coverAvailableProxy >= minCover && !
isDeepFrozen(
view, brokerOwner, asset) &&
393 auto const brokerPayee = sendBrokerFeeToOwner ? brokerOwner : brokerPseudoAccount;
395 if (!sendBrokerFeeToOwner)
401 JLOG(
j_.warn()) <<
"Both Loan Broker and Loan Broker pseudo-account "
402 "can not receive funds (deep frozen).";
413 if (loanSle->isFlag(lsfLoanImpaired))
417 JLOG(
j_.fatal()) <<
"Failed to unimpair loan before payment.";
424 if (tx.isFlag(tfLoanLatePayment))
426 if (tx.isFlag(tfLoanFullPayment))
428 if (tx.isFlag(tfLoanOverpayment))
433 std::expected<LoanPaymentParts, TER>
const paymentParts =
439 paymentParts.error(),
"xrpl::LoanPay::doApply",
"payment error is an error");
440 return paymentParts.error();
445 view.update(loanSle);
449 paymentParts->principalPaid >= 0,
450 "xrpl::LoanPay::doApply",
451 "valid principal paid");
454 paymentParts->interestPaid >= 0,
455 "xrpl::LoanPay::doApply",
456 "valid interest paid");
459 paymentParts->principalPaid + paymentParts->interestPaid > 0,
460 "xrpl::LoanPay::doApply",
462 XRPL_ASSERT_PARTS(paymentParts->feePaid >= 0,
"xrpl::LoanPay::doApply",
"valid fee paid");
464 if (paymentParts->principalPaid < 0 || paymentParts->interestPaid < 0 ||
465 paymentParts->feePaid < 0)
468 JLOG(
j_.fatal()) <<
"Loan payment computation returned invalid values.";
473 auto const [assetsTotalDelta, debtTotalDelta] =
loanPaymentDeltas(vaultSle, *paymentParts);
475 JLOG(
j_.debug()) <<
"Loan Pay: principal paid: " << paymentParts->principalPaid
476 <<
", interest paid: " << paymentParts->interestPaid
477 <<
", fee paid: " << paymentParts->feePaid
478 <<
", assets total delta: " << assetsTotalDelta
479 <<
", debt total delta: " << debtTotalDelta;
483 view.update(brokerSle);
485 auto assetsAvailableProxy = vaultSle->at(sfAssetsAvailable);
486 auto assetsTotalProxy = vaultSle->at(sfAssetsTotal);
488 auto const totalPaidToVaultRaw = paymentParts->principalPaid + paymentParts->interestPaid;
489 auto const totalPaidToVaultRounded =
492 !asset.integral() || totalPaidToVaultRaw == totalPaidToVaultRounded,
493 "xrpl::LoanPay::doApply",
494 "rounding does nothing for integral asset");
495 auto const totalPaidToBroker = paymentParts->feePaid;
498 (totalPaidToVaultRaw + totalPaidToBroker) ==
499 (paymentParts->principalPaid + paymentParts->interestPaid + paymentParts->feePaid),
500 "xrpl::LoanPay::doApply",
507 isRounded(asset, debtTotalDelta, loanScale),
508 "xrpl::LoanPay::doApply",
509 "debtTotalDelta rounding good");
517 view.update(vaultSle);
519 Number const assetsAvailableBefore = *assetsAvailableProxy;
520 Number const assetsTotalBefore = *assetsTotalProxy;
532 assetsAvailableBefore == pseudoAccountBalanceBefore,
533 "xrpl::LoanPay::doApply",
534 "vault pseudo balance agrees before");
538 assetsAvailableProxy += totalPaidToVaultRounded;
539 assetsTotalProxy += assetsTotalDelta;
542 *assetsAvailableProxy <= *assetsTotalProxy,
543 "xrpl::LoanPay::doApply",
544 "assets available must not be greater than assets outstanding");
546 JLOG(
j_.debug()) <<
"total paid to vault raw: " << totalPaidToVaultRaw
547 <<
", total paid to vault rounded: " << totalPaidToVaultRounded
548 <<
", total paid to broker: " << totalPaidToBroker
549 <<
", amount from transaction: " << amount;
553 totalPaidToVaultRounded + totalPaidToBroker <= amount,
554 "xrpl::LoanPay::doApply",
555 "amount is sufficient");
557 if (!sendBrokerFeeToOwner)
563 coverAvailableProxy += totalPaidToBroker;
571 Number const assetsAvailableAfter = *assetsAvailableProxy;
572 Number const assetsTotalAfter = *assetsTotalProxy;
575 assetsAvailableAfter <= assetsTotalAfter,
576 "xrpl::LoanPay::doApply",
577 "assets available must not be greater than assets outstanding");
578 if (assetsAvailableAfter == assetsAvailableBefore)
586 JLOG(
j_.warn()) <<
"LoanPay: Vault assets available unchanged after rounding: "
587 <<
"Before: " << assetsAvailableBefore
588 <<
", After: " << assetsAvailableAfter;
592 if (assetsTotalDelta !=
beast::kZero && assetsTotalAfter == assetsTotalBefore)
601 <<
"LoanPay: Vault assets expected change, but unchanged after rounding: "
602 <<
"Before: " << assetsTotalBefore
603 <<
", After: " << assetsTotalAfter
604 <<
", AssetsTotalDelta: " << assetsTotalDelta;
608 if (assetsTotalDelta ==
beast::kZero && assetsTotalAfter != assetsTotalBefore)
615 JLOG(
j_.fatal()) <<
"LoanPay: Vault assets changed unexpectedly after rounding: "
616 <<
"Before: " << assetsTotalBefore
617 <<
", After: " << assetsTotalAfter
618 <<
", AssetsTotalDelta: " << assetsTotalDelta;
622 if (assetsAvailableAfter > assetsTotalAfter)
626 JLOG(
j_.fatal()) <<
"LoanPay: Vault assets available must not be greater "
627 "than assets outstanding. Available: "
628 << assetsAvailableAfter <<
", Total: " << assetsTotalAfter;
634 auto const accountBalanceBefore = conservationBalance(
view,
accountID_, asset,
j_);
635 auto const vaultBalanceBefore =
accountID_ == vaultPseudoAccount
637 : conservationBalance(
view, vaultPseudoAccount, asset,
j_);
638 auto const brokerBalanceBefore =
accountID_ == brokerPayee
640 : conservationBalance(
view, brokerPayee, asset,
j_);
645 bool const skipPayeeAuth =
view.rules().enabled(fixCleanup3_4_0);
647 if (!skipPayeeAuth && totalPaidToVaultRounded !=
beast::kZero)
659 ctx_.getApplyViewContext(),
661 brokerPayeeSle->at(sfBalance).value().xrp(),
682 {{vaultPseudoAccount, totalPaidToVaultRounded}, {brokerPayee, totalPaidToBroker}},
697 assetsAvailableAfter == pseudoAccountBalanceAfter,
698 "xrpl::LoanPay::doApply",
699 "vault pseudo balance agrees after");
704 auto const accountBalanceAfter = conservationBalance(
view,
accountID_, asset,
j_);
705 auto const vaultBalanceAfter =
accountID_ == vaultPseudoAccount
707 : conservationBalance(
view, vaultPseudoAccount, asset,
j_);
708 auto const brokerBalanceAfter =
accountID_ == brokerPayee
710 : conservationBalance(
view, brokerPayee, asset,
j_);
711 auto const balanceScale = [&]() {
721 for (
auto const& a : {
722 accountBalanceBefore,
734 if (exponents.
empty())
736 UNREACHABLE(
"xrpl::LoanPay::doApply : all zeroes");
740 auto const min = *minItr;
741 auto const max = *maxItr;
742 JLOG(
j_.trace()) <<
"Min scale: " << min <<
", max scale: " << max;
753 "xrpl::LoanPay::doApply",
754 "Number rounding ToNearest");
757 auto const accountBalanceBeforeRounded =
roundToScale(accountBalanceBefore, balanceScale);
758 auto const vaultBalanceBeforeRounded =
roundToScale(vaultBalanceBefore, balanceScale);
759 auto const brokerBalanceBeforeRounded =
roundToScale(brokerBalanceBefore, balanceScale);
761 auto const totalBalanceBefore = accountBalanceBefore + vaultBalanceBefore + brokerBalanceBefore;
762 auto const totalBalanceBeforeRounded =
roundToScale(totalBalanceBefore, balanceScale);
764 JLOG(
j_.trace()) <<
"Before: "
765 <<
"account " <<
Number(accountBalanceBeforeRounded) <<
" ("
766 <<
Number(accountBalanceBefore) <<
")"
767 <<
", vault " <<
Number(vaultBalanceBeforeRounded) <<
" ("
768 <<
Number(vaultBalanceBefore) <<
")"
769 <<
", broker " <<
Number(brokerBalanceBeforeRounded) <<
" ("
770 <<
Number(brokerBalanceBefore) <<
")"
771 <<
", total " <<
Number(totalBalanceBeforeRounded) <<
" ("
772 <<
Number(totalBalanceBefore) <<
")";
774 auto const accountBalanceAfterRounded =
roundToScale(accountBalanceAfter, balanceScale);
775 auto const vaultBalanceAfterRounded =
roundToScale(vaultBalanceAfter, balanceScale);
776 auto const brokerBalanceAfterRounded =
roundToScale(brokerBalanceAfter, balanceScale);
778 auto const totalBalanceAfter = accountBalanceAfter + vaultBalanceAfter + brokerBalanceAfter;
779 auto const totalBalanceAfterRounded =
roundToScale(totalBalanceAfter, balanceScale);
781 JLOG(
j_.trace()) <<
"After: "
782 <<
"account " <<
Number(accountBalanceAfterRounded) <<
" ("
783 <<
Number(accountBalanceAfter) <<
")"
784 <<
", vault " <<
Number(vaultBalanceAfterRounded) <<
" ("
785 <<
Number(vaultBalanceAfter) <<
")"
786 <<
", broker " <<
Number(brokerBalanceAfterRounded) <<
" ("
787 <<
Number(brokerBalanceAfter) <<
")"
788 <<
", total " <<
Number(totalBalanceAfterRounded) <<
" ("
789 <<
Number(totalBalanceAfter) <<
")";
791 auto const accountBalanceChange = accountBalanceAfter - accountBalanceBefore;
792 auto const vaultBalanceChange = vaultBalanceAfter - vaultBalanceBefore;
793 auto const brokerBalanceChange = brokerBalanceAfter - brokerBalanceBefore;
795 auto const totalBalanceChange = accountBalanceChange + vaultBalanceChange + brokerBalanceChange;
796 auto const totalBalanceChangeRounded =
roundToScale(totalBalanceChange, balanceScale);
798 JLOG(
j_.trace()) <<
"Changes: "
799 <<
"account " <<
to_string(accountBalanceChange)
800 <<
", vault " <<
to_string(vaultBalanceChange)
801 <<
", broker " <<
to_string(brokerBalanceChange)
802 <<
", total " <<
to_string(totalBalanceChangeRounded) <<
" ("
803 <<
Number(totalBalanceChange) <<
")";
805 bool const goodRounding = totalBalanceBeforeRounded == totalBalanceAfterRounded ||
807 if (totalBalanceBeforeRounded != totalBalanceAfterRounded)
809 JLOG((goodRounding ?
j_.debug() :
j_.warn()))
810 <<
"Total rounded balances don't match"
811 << (totalBalanceChangeRounded ==
beast::kZero ?
", but total changes do" :
"");
815 JLOG((goodRounding ?
j_.debug() :
j_.warn()))
816 <<
"Total balance changes don't match"
817 << (totalBalanceBeforeRounded == totalBalanceAfterRounded ?
", but total balances do"
824 goodRounding,
"xrpl::LoanPay::doApply",
"funds are conserved (with rounding)");
827 accountBalanceAfter < accountBalanceBefore ||
accountID_ == asset.getIssuer(),
828 "xrpl::LoanPay::doApply",
829 "account balance decreased");
832 "xrpl::LoanPay::doApply",
833 "non-negative vault and broker balances");
835 vaultBalanceAfter >= vaultBalanceBefore,
836 "xrpl::LoanPay::doApply",
837 "vault balance did not decrease");
839 brokerBalanceAfter >= brokerBalanceBefore,
840 "xrpl::LoanPay::doApply",
841 "broker balance did not decrease");
843 vaultBalanceAfter > vaultBalanceBefore || brokerBalanceAfter > brokerBalanceBefore,
844 "xrpl::LoanPay::doApply",
845 "vault and/or broker balance increased");