26 using namespace Lending;
28 auto const& tx = ctx.
tx;
32 !tx.isFieldPresent(sfCounterparty))
35 JLOG(ctx.
j.
debug()) <<
"BatchTrace[" << parentBatchId <<
"]: "
36 <<
"no Counterparty for inner LoanSet transaction.";
42 if (tx.isFieldPresent(sfCounterpartySignature))
43 return tx.getFieldObject(sfCounterpartySignature);
48 JLOG(ctx.
j.
warn()) <<
"LoanSet transaction must have a CounterpartySignature.";
58 if (
auto const data = tx[~sfData];
61 for (
auto const& field : {&sfLoanServiceFee, &sfLatePaymentFee, &sfClosePaymentFee})
67 auto const p = tx[sfPrincipalRequested];
80 if (!
validNumericRange(tx[~sfOverpaymentInterestRate], maxOverpaymentInterestRate))
83 if (
auto const paymentTotal = tx[~sfPaymentTotal]; paymentTotal && *paymentTotal <= 0)
86 auto const paymentInterval = tx[~sfPaymentInterval];
105 if (
auto const brokerID = ctx.
tx[~sfLoanBrokerID]; brokerID && *brokerID == beast::zero)
190 auto const& tx = ctx.
tx;
198 using timeType =
decltype(sfNextPaymentDueDate)::type::value_type;
201 static_assert(maxTime == 4'294'967'295);
211 if (grace > timeAvailable)
213 JLOG(ctx.
j.
warn()) <<
"Grace period exceeds protocol time limit.";
217 if (interval > timeAvailable)
219 JLOG(ctx.
j.
warn()) <<
"Payment interval exceeds protocol time limit.";
223 if (total > timeAvailable)
225 JLOG(ctx.
j.
warn()) <<
"Payment total exceeds protocol time limit.";
229 auto const timeLastPayment = timeAvailable - grace;
231 if (timeLastPayment / interval < total)
233 JLOG(ctx.
j.
warn()) <<
"Last payment due date, or grace period for "
234 "last payment exceeds protocol time limit.";
239 auto const account = tx[sfAccount];
240 auto const brokerID = tx[sfLoanBrokerID];
247 JLOG(ctx.
j.
warn()) <<
"LoanBroker does not exist.";
250 auto const brokerOwner = brokerSle->at(sfOwner);
251 auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
252 if (account != brokerOwner && counterparty != brokerOwner)
254 JLOG(ctx.
j.
warn()) <<
"Neither Account nor Counterparty are the owner "
255 "of the LoanBroker.";
258 auto const brokerPseudo = brokerSle->at(sfAccount);
260 auto const borrower = counterparty == brokerOwner ? account : counterparty;
265 JLOG(ctx.
j.
warn()) <<
"Borrower does not exist.";
276 if (vault->at(sfAssetsMaximum) != 0 && vault->at(sfAssetsTotal) >= vault->at(sfAssetsMaximum))
278 JLOG(ctx.
j.
warn()) <<
"Vault at maximum assets limit. Can't add another loan.";
282 Asset const asset = vault->at(sfAsset);
284 auto const vaultPseudo = vault->at(sfAccount);
292 if (
auto const value = tx[field]; value &&
STAmount{asset, *value} != *value)
294 JLOG(ctx.
j.
warn()) << field.f->getName() <<
" (" << *value
295 <<
") can not be represented as a(n) " <<
to_string(asset) <<
".";
306 JLOG(ctx.
j.
warn()) <<
"Vault pseudo-account is frozen.";
315 JLOG(ctx.
j.
warn()) <<
"Broker pseudo-account is frozen.";
325 JLOG(ctx.
j.
warn()) <<
"Borrower account is frozen.";
332 JLOG(ctx.
j.
warn()) <<
"Broker owner account is frozen.";
345 auto const brokerID = tx[sfLoanBrokerID];
350 auto const brokerOwner = brokerSle->at(sfOwner);
355 auto const vaultSle =
view.
peek(keylet ::vault(brokerSle->at(sfVaultID)));
358 auto const vaultPseudo = vaultSle->at(sfAccount);
359 Asset const vaultAsset = vaultSle->at(sfAsset);
361 auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
362 auto const borrower = counterparty == brokerOwner ?
account_ : counterparty;
369 auto const brokerPseudo = brokerSle->at(sfAccount);
371 if (!brokerPseudoSle)
375 auto const principalRequested = tx[sfPrincipalRequested];
377 auto vaultAvailableProxy = vaultSle->at(sfAssetsAvailable);
378 auto vaultTotalProxy = vaultSle->at(sfAssetsTotal);
380 if (vaultAvailableProxy < principalRequested)
382 JLOG(
j_.
warn()) <<
"Insufficient assets available in the Vault to fund the loan.";
386 TenthBips32 const interestRate{tx[~sfInterestRate].value_or(0)};
401 properties.loanState.valueOutstanding,
403 properties.loanState.managementFeeDue);
405 auto const vaultMaximum = *vaultSle->at(sfAssetsMaximum);
407 vaultMaximum == 0 || vaultMaximum > *vaultTotalProxy,
408 "xrpl::LoanSet::doApply",
409 "Vault is below maximum limit");
410 if (vaultMaximum != 0 && state.interestDue > vaultMaximum - vaultTotalProxy)
412 JLOG(
j_.
warn()) <<
"Loan would exceed the maximum assets of the vault";
419 if (
auto const value = tx[field];
420 value && !
isRounded(vaultAsset, *value, properties.loanScale))
422 JLOG(
j_.
warn()) << field.f->getName() <<
" (" << *value
423 <<
") has too much precision. Total loan value is "
424 << properties.loanState.valueOutstanding <<
" with a scale of "
425 << properties.loanScale;
433 interestRate != beast::zero,
440 if (properties.loanState.managementFeeDue < 0 || properties.loanState.valueOutstanding <= 0 ||
441 properties.periodicPayment <= 0)
444 JLOG(
j_.
warn()) <<
"Computed loan properties are invalid. Does not compute."
445 <<
" Management fee: " << properties.loanState.managementFeeDue
446 <<
". Total Value: " << properties.loanState.valueOutstanding
447 <<
". PeriodicPayment: " << properties.periodicPayment;
452 auto const originationFee = tx[~sfLoanOriginationFee].value_or(
Number{});
454 auto const loanAssetsToBorrower = principalRequested - originationFee;
456 auto const newDebtDelta = principalRequested + state.interestDue;
457 auto const newDebtTotal = brokerSle->at(sfDebtTotal) + newDebtDelta;
458 if (
auto const debtMaximum = brokerSle->at(sfDebtMaximum);
459 debtMaximum != 0 && debtMaximum < newDebtTotal)
461 JLOG(
j_.
warn()) <<
"Loan would exceed the maximum debt limit of the LoanBroker.";
464 TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)};
470 if (brokerSle->at(sfCoverAvailable) <
tenthBipsOfValue(newDebtTotal, coverRateMinimum))
472 JLOG(
j_.
warn()) <<
"Insufficient first-loss capital to cover the loan.";
479 auto const ownerCount = borrowerSle->at(sfOwnerCount);
493 borrower ==
account_ || borrower == counterparty,
494 "xrpl::LoanSet::doApply",
495 "borrower signed transaction");
497 view, borrower, borrowerSle->at(sfBalance).value().xrp(), vaultAsset,
j_);
510 if (originationFee != beast::zero)
515 brokerOwner ==
account_ || brokerOwner == counterparty,
516 "xrpl::LoanSet::doApply",
517 "broker owner signed transaction");
520 view, brokerOwner, brokerOwnerSle->at(sfBalance).value().xrp(), vaultAsset,
j_);
536 {{borrower, loanAssetsToBorrower}, {brokerOwner, originationFee}},
543 auto loanSequenceProxy = brokerSle->at(sfLoanSequence);
549 auto setLoanField = [&loan, &tx](
auto const& field,
std::uint32_t const defValue = 0) {
552 loan->at(field) = tx[field].value_or(defValue);
556 loan->at(sfLoanScale) = properties.loanScale;
557 loan->at(sfStartDate) = startDate;
558 loan->at(sfPaymentInterval) = paymentInterval;
559 loan->at(sfLoanSequence) = *loanSequenceProxy;
560 loan->at(sfLoanBrokerID) = brokerID;
561 loan->at(sfBorrower) = borrower;
563 if (tx.isFlag(tfLoanOverpayment))
564 loan->setFlag(lsfLoanOverpayment);
565 setLoanField(~sfLoanOriginationFee);
566 setLoanField(~sfLoanServiceFee);
567 setLoanField(~sfLatePaymentFee);
568 setLoanField(~sfClosePaymentFee);
569 setLoanField(~sfOverpaymentFee);
570 setLoanField(~sfInterestRate);
571 setLoanField(~sfLateInterestRate);
572 setLoanField(~sfCloseInterestRate);
573 setLoanField(~sfOverpaymentInterestRate);
576 loan->at(sfPrincipalOutstanding) = principalRequested;
577 loan->at(sfPeriodicPayment) = properties.periodicPayment;
578 loan->at(sfTotalValueOutstanding) = properties.loanState.valueOutstanding;
579 loan->at(sfManagementFeeOutstanding) = properties.loanState.managementFeeDue;
580 loan->at(sfPreviousPaymentDueDate) = 0;
581 loan->at(sfNextPaymentDueDate) = startDate + paymentInterval;
582 loan->at(sfPaymentRemaining) = paymentTotal;
586 vaultAvailableProxy -= principalRequested;
587 vaultTotalProxy += state.interestDue;
589 *vaultAvailableProxy <= *vaultTotalProxy,
590 "xrpl::LoanSet::doApply",
591 "assets available must not be greater than assets outstanding");
599 loanSequenceProxy += 1;
602 if (loanSequenceProxy == 0)
607 if (
auto const ter =
dirLink(
view, brokerPseudo, loan, sfLoanBrokerNode))
610 if (
auto const ter =
dirLink(
view, borrower, loan, sfOwnerNode))
State information when determining if a tx is likely to claim a fee.