xrpld
Loading...
Searching...
No Matches
LoanSet.cpp
1#include <xrpl/tx/transactors/lending/LoanSet.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/Number.h>
5#include <xrpl/beast/utility/Zero.h>
6#include <xrpl/beast/utility/instrumentation.h>
7#include <xrpl/core/ServiceRegistry.h>
8#include <xrpl/ledger/View.h>
9#include <xrpl/ledger/helpers/AccountRootHelpers.h>
10#include <xrpl/ledger/helpers/LendingHelpers.h>
11#include <xrpl/ledger/helpers/SponsorHelpers.h>
12#include <xrpl/ledger/helpers/TokenHelpers.h>
13#include <xrpl/ledger/helpers/VaultHelpers.h>
14#include <xrpl/protocol/AccountID.h>
15#include <xrpl/protocol/Asset.h>
16#include <xrpl/protocol/Feature.h>
17#include <xrpl/protocol/Indexes.h>
18#include <xrpl/protocol/LedgerFormats.h>
19#include <xrpl/protocol/Protocol.h>
20#include <xrpl/protocol/SField.h>
21#include <xrpl/protocol/STLedgerEntry.h>
22#include <xrpl/protocol/STNumber.h>
23#include <xrpl/protocol/STObject.h>
24#include <xrpl/protocol/STTakesAsset.h>
25#include <xrpl/protocol/STTx.h>
26#include <xrpl/protocol/SeqProxy.h>
27#include <xrpl/protocol/TER.h>
28#include <xrpl/protocol/TxFlags.h>
29#include <xrpl/protocol/Units.h>
30#include <xrpl/protocol/XRPAmount.h>
31#include <xrpl/tx/Transactor.h>
32
33#include <cstddef>
34#include <cstdint>
35#include <limits>
36#include <memory>
37#include <optional>
38#include <type_traits>
39#include <vector>
40
41namespace xrpl {
42
43bool
48
51{
52 return tfLoanSetMask;
53}
54
57{
58 using namespace lending;
59
60 auto const& tx = ctx.tx;
61
62 if (tx.isFieldPresent(sfSponsorFlags) && isReserveSponsored(tx))
63 {
64 JLOG(ctx.j.debug()) << "LoanSet: reserve sponsorship is not allowed.";
65 return temINVALID_FLAG;
66 }
67
68 // Special case for Batch inner transactions
69 if (tx.isFlag(tfInnerBatchTxn) && ctx.rules.enabled(featureBatchV1_1) &&
70 !tx.isFieldPresent(sfCounterparty))
71 {
72 auto const parentBatchId = ctx.parentBatchId.value_or(uint256{0});
73 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
74 << "no Counterparty for inner LoanSet transaction.";
75 return temBAD_SIGNER;
76 }
77
78 // These extra hoops are because STObjects cannot be Proxy'd from STObject.
79 auto const counterPartySig = [&tx]() -> std::optional<STObject const> {
80 if (tx.isFieldPresent(sfCounterpartySignature))
81 return tx.getFieldObject(sfCounterpartySignature);
82 return std::nullopt;
83 }();
84 if (!tx.isFlag(tfInnerBatchTxn) && !counterPartySig)
85 {
86 JLOG(ctx.j.warn()) << "LoanSet transaction must have a CounterpartySignature.";
87 return temBAD_SIGNER;
88 }
89
90 if (counterPartySig)
91 {
92 if (auto const ret = xrpl::detail::preflightCheckSigningKey(*counterPartySig, ctx.j))
93 return ret;
94 }
95
96 if (auto const data = tx[~sfData];
97 data && !data->empty() && !validDataLength(tx[~sfData], kMaxDataPayloadLength))
98 return temINVALID;
99 for (auto const& field : {&sfLoanServiceFee, &sfLatePaymentFee, &sfClosePaymentFee})
100 {
101 if (!validNumericMinimum(tx[~*field]))
102 return temINVALID;
103 }
104 // Principal Requested is required
105 auto const p = tx[sfPrincipalRequested];
106 if (p <= 0)
107 return temINVALID;
108 if (!validNumericRange(tx[~sfLoanOriginationFee], p))
109 return temINVALID;
110 if (!validNumericRange(tx[~sfInterestRate], kMaxInterestRate))
111 return temINVALID;
112 if (!validNumericRange(tx[~sfOverpaymentFee], kMaxOverpaymentFee))
113 return temINVALID;
114 if (!validNumericRange(tx[~sfLateInterestRate], kMaxLateInterestRate))
115 return temINVALID;
116 if (!validNumericRange(tx[~sfCloseInterestRate], kMaxCloseInterestRate))
117 return temINVALID;
118 if (!validNumericRange(tx[~sfOverpaymentInterestRate], kMaxOverpaymentInterestRate))
119 return temINVALID;
120
121 if (auto const paymentTotal = tx[~sfPaymentTotal]; paymentTotal && *paymentTotal <= 0)
122 return temINVALID;
123
124 auto const paymentInterval = tx[~sfPaymentInterval];
126 return temINVALID; // Grace period is between min default value and payment interval
127 if (auto const gracePeriod = tx[~sfGracePeriod]; !validNumericRange(
128 gracePeriod,
129 paymentInterval.value_or(LoanSet::kDefaultPaymentInterval),
131 {
132 return temINVALID;
133 }
134
135 // Copied from preflight2
136 if (counterPartySig)
137 {
138 if (auto const ret =
139 xrpl::detail::preflightCheckSimulateKeys(ctx.flags, *counterPartySig, ctx.j))
140 return *ret;
141 }
142
143 if (auto const brokerID = ctx.tx[~sfLoanBrokerID]; brokerID && *brokerID == beast::kZero)
144 return temINVALID;
145
146 return tesSUCCESS;
147}
148
149NotTEC
151{
152 if (auto ret = Transactor::checkSign(ctx))
153 return ret;
154
155 // Counter signer is optional. If it's not specified, it's assumed to be
156 // `LoanBroker.Owner`. Note that we have not checked whether the
157 // loanbroker exists at this point.
158 auto const counterSigner = [&]() -> std::optional<AccountID> {
159 if (auto const c = ctx.tx.at(~sfCounterparty))
160 return c;
161
162 if (auto const broker = ctx.view.read(keylet::loanBroker(ctx.tx[sfLoanBrokerID])))
163 return broker->at(sfOwner);
164 return std::nullopt;
165 }();
166 if (!counterSigner)
167 return temBAD_SIGNER;
168
169 // Counterparty signature is optional. Presence is checked in preflight.
170 if (!ctx.tx.isFieldPresent(sfCounterpartySignature))
171 return tesSUCCESS;
172 auto const counterSig = ctx.tx.getFieldObject(sfCounterpartySignature);
174 ctx.view, ctx.flags, ctx.parentBatchId, *counterSigner, counterSig, ctx.j);
175}
176
179{
180 auto const normalCost = Transactor::calculateBaseFee(view, tx);
181
182 // Compute the additional cost of each signature in the
183 // CounterpartySignature, whether a single signature or a multisignature
184 XRPAmount const baseFee = view.fees().base;
185
186 // Counterparty signature is optional, but getFieldObject will return an
187 // empty object if it's not present.
188 auto const counterSig = tx.getFieldObject(sfCounterpartySignature);
189 // Each signer adds one more baseFee to the minimum required fee
190 // for the transaction. Note that unlike the base class, the single signer
191 // is counted if present. It will only be absent in a batch inner
192 // transaction.
193 std::size_t const signerCount = [&counterSig]() -> int {
194 // Compute defensively.
195 // Assure that "tx" cannot be accessed and cause confusion or miscalculations.
196 if (counterSig.isFieldPresent(sfSigners))
197 return counterSig.getFieldArray(sfSigners).size();
198 return counterSig.isFieldPresent(sfTxnSignature) ? 1 : 0;
199 }();
200
201 return normalCost + (signerCount * baseFee);
202}
203
206{
207 static std::vector<OptionaledField<STNumber>> const kValueFields{
208 ~sfPrincipalRequested,
209 ~sfLoanOriginationFee,
210 ~sfLoanServiceFee,
211 ~sfLatePaymentFee,
212 ~sfClosePaymentFee
213 // Overpayment fee is really a rate. Don't check it here.
214 };
215
216 return kValueFields;
217}
218
219static std::uint32_t
221{
222 return view.header().closeTime.time_since_epoch().count();
223}
224
225TER
227{
228 auto const& tx = ctx.tx;
229 auto const interval = ctx.tx.at(~sfPaymentInterval).value_or(kDefaultPaymentInterval);
230 auto const total = ctx.tx.at(~sfPaymentTotal).value_or(kDefaultPaymentTotal);
231
232 {
233 // Check for numeric overflow of the schedule before we load any
234 // objects. The Grace Period for the last payment ends at:
235 // startDate + (paymentInterval * paymentTotal) + gracePeriod.
236 // If that value is larger than "maxTime", the value
237 // overflows, and we kill the transaction.
238 using timeType = decltype(sfNextPaymentDueDate)::type::value_type;
240 constexpr timeType kMaxTime = std::numeric_limits<timeType>::max();
241 static_assert(kMaxTime == 4'294'967'295);
242
243 auto const timeAvailable = kMaxTime - getStartDate(ctx.view);
244 auto const grace = ctx.tx.at(~sfGracePeriod).value_or(kDefaultGracePeriod);
245
246 // The grace period can't be larger than the interval. Check it first,
247 // mostly so that unit tests can test that specific case.
248 if (grace > timeAvailable)
249 {
250 JLOG(ctx.j.warn()) << "Grace period exceeds protocol time limit.";
251 return tecKILLED;
252 }
253
254 if (interval > timeAvailable)
255 {
256 JLOG(ctx.j.warn()) << "Payment interval exceeds protocol time limit.";
257 return tecKILLED;
258 }
259
260 if (total > timeAvailable)
261 {
262 JLOG(ctx.j.warn()) << "Payment total exceeds protocol time limit.";
263 return tecKILLED;
264 }
265
266 auto const timeLastPayment = timeAvailable - grace;
267
268 if (timeLastPayment / interval < total)
269 {
270 JLOG(ctx.j.warn()) << "Last payment due date, or grace period for "
271 "last payment exceeds protocol time limit.";
272 return tecKILLED;
273 }
274 }
275
276 auto const account = tx[sfAccount];
277 auto const brokerID = tx[sfLoanBrokerID];
278
279 auto const brokerSle = ctx.view.read(keylet::loanBroker(brokerID));
280 if (!brokerSle)
281 {
282 // This can only be hit if there's a counterparty specified, otherwise
283 // it'll fail in the signature check
284 JLOG(ctx.j.warn()) << "LoanBroker does not exist.";
285 return tecNO_ENTRY;
286 }
287 auto const brokerOwner = brokerSle->at(sfOwner);
288 auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
289 if (account != brokerOwner && counterparty != brokerOwner)
290 {
291 JLOG(ctx.j.warn()) << "Neither Account nor Counterparty are the owner "
292 "of the LoanBroker.";
293 return tecNO_PERMISSION;
294 }
295 auto const brokerPseudo = brokerSle->at(sfAccount);
296
297 auto const borrower = counterparty == brokerOwner ? account : counterparty;
298 if (auto const borrowerSle = ctx.view.read(keylet::account(borrower)); !borrowerSle)
299 {
300 // It may not be possible to hit this case, because it'll fail the
301 // signature check with terNO_ACCOUNT.
302 JLOG(ctx.j.warn()) << "Borrower does not exist.";
303 return terNO_ACCOUNT;
304 }
305
306 auto const vault = ctx.view.read(keylet::vault(brokerSle->at(sfVaultID)));
307 if (!vault)
308 {
309 // Should be impossible
310 return tefBAD_LEDGER; // LCOV_EXCL_LINE
311 }
312
313 if (ctx.view.rules().enabled(featureLendingProtocolV1_1))
314 {
315 auto const phase = getVaultPhase(ctx.view, vault);
316 if (phase == VaultPhase::Subscription)
317 {
318 JLOG(ctx.j.warn()) << "Vault is still in the subscription phase.";
319 return tecTOO_SOON;
320 }
321 if (phase == VaultPhase::Redemption)
322 {
323 JLOG(ctx.j.warn()) << "Vault has entered the redemption phase.";
324 return tecEXPIRED;
325 }
326 if (phase == VaultPhase::Investment)
327 {
328 auto const finalPayment =
329 std::uint64_t{getStartDate(ctx.view)} + (std::uint64_t{interval} * total);
330 if (finalPayment >= vault->at(sfRedemptionDate))
331 {
332 JLOG(ctx.j.warn()) << "Final loan payment date is on or after "
333 "the vault's redemption date.";
334 return tecNO_PERMISSION;
335 }
336 }
337 }
338
339 if (vault->at(sfAssetsMaximum) != 0 && vault->at(sfAssetsTotal) >= vault->at(sfAssetsMaximum))
340 {
341 JLOG(ctx.j.warn()) << "Vault at maximum assets limit. Can't add another loan.";
342 return tecLIMIT_EXCEEDED;
343 }
344
345 Asset const asset = vault->at(sfAsset);
346
347 auto const vaultPseudo = vault->at(sfAccount);
348
349 // Check that relevant values can be represented as the vault asset type.
350 // This check is almost duplicated in doApply, but that check is done after
351 // the overall loan scale is known. This is mostly only relevant for
352 // integral (non-IOU) types
353 for (auto const& field : getValueFields())
354 {
355 if (auto const value = tx[field]; value && STAmount{asset, *value} != *value)
356 {
357 JLOG(ctx.j.warn()) << field.f->getName() << " (" << *value
358 << ") can not be represented as a(n) " << to_string(asset) << ".";
359 return tecPRECISION_LOSS;
360 }
361 }
362
363 if (auto const ter = canAddHolding(ctx.view, asset))
364 return ter;
365
366 // vaultPseudo is going to send funds, so it can't be frozen.
367 if (auto const ret = checkFrozen(ctx.view, vaultPseudo, asset))
368 {
369 JLOG(ctx.j.warn()) << "Vault pseudo-account is frozen.";
370 return ret;
371 }
372
373 // brokerPseudo is the fallback account to receive LoanPay fees, even if the
374 // broker owner is unable to accept them. Don't create the loan if it is
375 // deep frozen.
376 if (auto const ret = checkDeepFrozen(ctx.view, brokerPseudo, asset))
377 {
378 JLOG(ctx.j.warn()) << "Broker pseudo-account is frozen.";
379 return ret;
380 }
381
382 // borrower is eventually going to have to pay back the loan, so it can't be
383 // frozen now. It is also going to receive funds, so it can't be deep
384 // frozen, but being frozen is a prerequisite for being deep frozen, so
385 // checking the one is sufficient.
386 if (auto const ret = checkFrozen(ctx.view, borrower, asset))
387 {
388 JLOG(ctx.j.warn()) << "Borrower account is frozen.";
389 return ret;
390 }
391 // brokerOwner is going to receive funds if there's an origination fee, so
392 // it can't be deep frozen
393 if (auto const ret = checkDeepFrozen(ctx.view, brokerOwner, asset))
394 {
395 JLOG(ctx.j.warn()) << "Broker owner account is frozen.";
396 return ret;
397 }
398
399 return tesSUCCESS;
400}
401
402TER
404{
405 auto const& tx = ctx_.tx;
406 auto& view = ctx_.view();
407
408 auto const brokerID = tx[sfLoanBrokerID];
409
410 auto const brokerSle = view.peek(keylet::loanBroker(brokerID));
411 if (!brokerSle)
412 return tefBAD_LEDGER; // LCOV_EXCL_LINE
413 auto const brokerOwner = brokerSle->at(sfOwner);
414 auto const brokerOwnerSle = view.peek(keylet::account(brokerOwner));
415 if (!brokerOwnerSle)
416 return tefBAD_LEDGER; // LCOV_EXCL_LINE
417
418 auto const vaultSle = view.peek(keylet ::vault(brokerSle->at(sfVaultID)));
419 if (!vaultSle)
420 return tefBAD_LEDGER; // LCOV_EXCL_LINE
421 auto const vaultPseudo = vaultSle->at(sfAccount);
422 Asset const vaultAsset = vaultSle->at(sfAsset);
423
424 auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
425 auto const borrower = counterparty == brokerOwner ? accountID_ : counterparty;
426 auto const borrowerSle = view.peek(keylet::account(borrower));
427 if (!borrowerSle)
428 {
429 return tefBAD_LEDGER; // LCOV_EXCL_LINE
430 }
431
432 auto const brokerPseudo = brokerSle->at(sfAccount);
433 auto const brokerPseudoSle = view.peek(keylet::account(brokerPseudo));
434 if (!brokerPseudoSle)
435 {
436 return tefBAD_LEDGER; // LCOV_EXCL_LINE
437 }
438 auto const principalRequested = tx[sfPrincipalRequested];
439
440 auto vaultAvailableProxy = vaultSle->at(sfAssetsAvailable);
441 auto vaultTotalProxy = vaultSle->at(sfAssetsTotal);
442 auto const vaultScale = getAssetsTotalScale(vaultSle);
443 if (vaultAvailableProxy < principalRequested)
444 {
445 JLOG(j_.warn()) << "Insufficient assets available in the Vault to fund the loan.";
447 }
448
449 TenthBips32 const interestRate{tx[~sfInterestRate].value_or(0)};
450
451 auto const paymentInterval = tx[~sfPaymentInterval].value_or(kDefaultPaymentInterval);
452 auto const paymentTotal = tx[~sfPaymentTotal].value_or(kDefaultPaymentTotal);
453
454 auto const properties = computeLoanProperties(
455 view.rules(),
456 vaultAsset,
457 principalRequested,
458 interestRate,
459 paymentInterval,
460 paymentTotal,
461 TenthBips16{brokerSle->at(sfManagementFeeRate)},
462 vaultScale);
463
464 LoanState const state = constructLoanState(
465 properties.loanState.valueOutstanding,
466 principalRequested,
467 properties.loanState.managementFeeDue);
468
469 XRPL_ASSERT_PARTS(
470 *vaultSle->at(sfAssetsMaximum) == 0 || *vaultSle->at(sfAssetsMaximum) > *vaultTotalProxy,
471 "xrpl::LoanSet::doApply",
472 "Vault is below maximum limit");
473
474 if (loanOriginationExceedsVaultMaximum(vaultSle, vaultTotalProxy, state.interestDue))
475 {
476 JLOG(j_.warn()) << "Loan would exceed the maximum assets of the vault";
477 return tecLIMIT_EXCEEDED;
478 }
479 // Check that relevant values won't lose precision. This is mostly only
480 // relevant for IOU assets.
481 for (auto const& field : getValueFields())
482 {
483 if (auto const value = tx[field];
484 value && !isRounded(vaultAsset, *value, properties.loanScale))
485 {
486 JLOG(j_.warn()) << field.f->getName() << " (" << *value
487 << ") has too much precision. Total loan value is "
488 << properties.loanState.valueOutstanding << " with a scale of "
489 << properties.loanScale;
490 return tecPRECISION_LOSS;
491 }
492 }
493
494 if (auto const ret = checkLoanGuards(
495 vaultAsset,
496 principalRequested,
497 interestRate != beast::kZero,
498 paymentTotal,
499 properties,
500 j_))
501 return ret;
502
503 // Check that the other computed values are valid
504 if (properties.loanState.managementFeeDue < 0 || properties.loanState.valueOutstanding <= 0 ||
505 properties.periodicPayment <= 0)
506 {
507 // LCOV_EXCL_START
508 JLOG(j_.warn()) << "Computed loan properties are invalid. Does not compute."
509 << " Management fee: " << properties.loanState.managementFeeDue
510 << ". Total Value: " << properties.loanState.valueOutstanding
511 << ". PeriodicPayment: " << properties.periodicPayment;
512 return tecINTERNAL;
513 // LCOV_EXCL_STOP
514 }
515
516 auto const originationFee = tx[~sfLoanOriginationFee].value_or(Number{});
517
518 auto const loanAssetsToBorrower = principalRequested - originationFee;
519
520 auto const [assetsTotalDelta, debtTotalDelta] =
521 loanOriginationDeltas(vaultSle, principalRequested, state.interestDue);
522 auto const newDebtTotal = brokerSle->at(sfDebtTotal) + debtTotalDelta;
523 if (auto const debtMaximum = brokerSle->at(sfDebtMaximum);
524 debtMaximum != 0 && debtMaximum < newDebtTotal)
525 {
526 JLOG(j_.warn()) << "Loan would exceed the maximum debt limit of the LoanBroker.";
527 return tecLIMIT_EXCEEDED;
528 }
529 TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)};
530 {
531 auto const minCover = [&]() {
532 if (ctx_.view().rules().enabled(fixCleanup3_2_0))
533 {
534 return minimumBrokerCover(newDebtTotal, coverRateMinimum, vaultSle);
535 }
536
537 // Round the minimum required cover up to be conservative. This ensures
538 // CoverAvailable never drops below the theoretical minimum, protecting
539 // the broker's solvency.
541 return tenthBipsOfValue(newDebtTotal, coverRateMinimum);
542 }();
543 if (brokerSle->at(sfCoverAvailable) < minCover)
544 {
545 JLOG(j_.warn()) << "Insufficient first-loss capital to cover the loan.";
547 }
548 }
549
550 increaseOwnerCount(view, borrowerSle, {}, 1, j_);
551
552 {
553 auto const balance =
554 accountID_ == borrower ? preFeeBalance_ : borrowerSle->at(sfBalance).value().xrp();
555 if (balance < accountReserve(view, borrowerSle, j_))
557 }
558
559 // Account for the origination fee using two payments
560 //
561 // 1. Transfer loanAssetsAvailable (principalRequested - originationFee)
562 // from vault pseudo-account to the borrower.
563 // Create a holding for the borrower if one does not already exist.
564
565 XRPL_ASSERT_PARTS(
566 borrower == accountID_ || borrower == counterparty,
567 "xrpl::LoanSet::doApply",
568 "borrower signed transaction");
569 auto applyViewContext = ctx_.getApplyViewContext();
570 if (auto const ter = addEmptyHolding(
571 applyViewContext, borrower, borrowerSle->at(sfBalance).value().xrp(), vaultAsset, j_);
572 ter && ter != tecDUPLICATE)
573 {
574 // ignore tecDUPLICATE. That means the holding already exists, and
575 // is fine here
576 return ter;
577 }
578
579 if (auto const ter = requireAuth(view, vaultAsset, borrower, AuthType::StrongAuth))
580 return ter;
581
582 // 2. Transfer originationFee, if any, from vault pseudo-account to
583 // LoanBroker owner.
584 if (originationFee != beast::kZero)
585 {
586 // Create the holding if it doesn't already exist (necessary for MPTs).
587 // The owner may have deleted their MPT / line at some point.
588 XRPL_ASSERT_PARTS(
589 brokerOwner == accountID_ || brokerOwner == counterparty,
590 "xrpl::LoanSet::doApply",
591 "broker owner signed transaction");
592
593 if (auto const ter = addEmptyHolding(
594 applyViewContext,
595 brokerOwner,
596 brokerOwnerSle->at(sfBalance).value().xrp(),
597 vaultAsset,
598 j_);
599 ter && ter != tecDUPLICATE)
600 {
601 // ignore tecDUPLICATE. That means the holding already exists,
602 // and is fine here
603 return ter;
604 }
605 }
606
607 if (auto const ter = requireAuth(view, vaultAsset, brokerOwner, AuthType::StrongAuth))
608 return ter;
609
610 if (auto const ter = accountSendMulti(
611 view,
612 vaultPseudo,
613 vaultAsset,
614 {{borrower, loanAssetsToBorrower}, {brokerOwner, originationFee}},
615 j_,
617 return ter;
618
619 // Get shortcuts to the loan property values
620 auto const startDate = getStartDate(view);
621 auto loanSequenceProxy = brokerSle->at(sfLoanSequence);
622
623 // Create the loan
624 auto loan =
625 std::make_shared<SLE>(keylet::loan(brokerID, SeqProxy::rawSequence(*loanSequenceProxy)));
626
627 // Prevent copy/paste errors
628 auto setLoanField = [&loan, &tx](auto const& field, std::uint32_t const defValue = 0) {
629 // at() is smart enough to unseat a default field set to the default
630 // value
631 loan->at(field) = tx[field].value_or(defValue);
632 };
633
634 // Set required and fixed tx fields
635 loan->at(sfLoanScale) = properties.loanScale;
636 loan->at(sfStartDate) = startDate;
637 loan->at(sfPaymentInterval) = paymentInterval;
638 loan->at(sfLoanSequence) = *loanSequenceProxy;
639 loan->at(sfLoanBrokerID) = brokerID;
640 loan->at(sfBorrower) = borrower;
641 // Set all other transaction fields directly from the transaction
642 if (tx.isFlag(tfLoanOverpayment))
643 loan->setFlag(lsfLoanOverpayment);
644 setLoanField(~sfLoanOriginationFee);
645 setLoanField(~sfLoanServiceFee);
646 setLoanField(~sfLatePaymentFee);
647 setLoanField(~sfClosePaymentFee);
648 setLoanField(~sfOverpaymentFee);
649 setLoanField(~sfInterestRate);
650 setLoanField(~sfLateInterestRate);
651 setLoanField(~sfCloseInterestRate);
652 setLoanField(~sfOverpaymentInterestRate);
653 setLoanField(~sfGracePeriod, kDefaultGracePeriod);
654 // Set dynamic / computed fields to their initial values
655 loan->at(sfPrincipalOutstanding) = principalRequested;
656 loan->at(sfPeriodicPayment) = properties.periodicPayment;
657 loan->at(sfTotalValueOutstanding) = properties.loanState.valueOutstanding;
658 loan->at(sfManagementFeeOutstanding) = properties.loanState.managementFeeDue;
659 loan->at(sfPreviousPaymentDueDate) = 0;
660 loan->at(sfNextPaymentDueDate) = startDate + paymentInterval;
661 loan->at(sfPaymentRemaining) = paymentTotal;
662 view.insert(loan);
663
664 // Update the balances in the vault
665 vaultAvailableProxy -= principalRequested;
666 vaultTotalProxy += assetsTotalDelta;
667 XRPL_ASSERT_PARTS(
668 *vaultAvailableProxy <= *vaultTotalProxy,
669 "xrpl::LoanSet::doApply",
670 "assets available must not be greater than assets outstanding");
671 view.update(vaultSle);
672
673 // Update the balances in the loan broker
674 adjustImpreciseNumber(brokerSle->at(sfDebtTotal), debtTotalDelta, vaultAsset, vaultScale);
675 adjustLoanBrokerOwnerCount(view, brokerSle, 1, j_);
676 loanSequenceProxy += 1;
677 // The sequence should be extremely unlikely to roll over, but fail if it
678 // does
679 if (loanSequenceProxy == 0)
681 view.update(brokerSle);
682
683 // Put the loan into the pseudo-account's directory
684 if (auto const ter = dirLink(view, brokerPseudo, loan, sfLoanBrokerNode))
685 return ter;
686 // Borrower is the owner of the loan
687 if (auto const ter = dirLink(view, borrower, loan, sfOwnerNode))
688 return ter;
689
690 associateAsset(*vaultSle, vaultAsset);
691 associateAsset(*brokerSle, vaultAsset);
692 associateAsset(*loan, vaultAsset);
693
694 return tesSUCCESS;
695}
696
697void
699{
700 // No transaction-specific invariants yet (future work).
701}
702
703bool
705{
706 // No transaction-specific invariants yet (future work).
707 return true;
708}
709
710//------------------------------------------------------------------------------
711
712} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:44
Stream debug() const
Definition Journal.h:344
Stream warn() const
Definition Journal.h:356
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
Definition LoanSet.cpp:50
static TER preclaim(PreclaimContext const &ctx)
Definition LoanSet.cpp:226
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Definition LoanSet.cpp:178
static constexpr std::uint32_t kDefaultPaymentTotal
static std::vector< OptionaledField< STNumber > > const & getValueFields()
Definition LoanSet.cpp:205
static NotTEC preflight(PreflightContext const &ctx)
Definition LoanSet.cpp:56
static NotTEC checkSign(PreclaimContext const &ctx)
Definition LoanSet.cpp:150
static constexpr std::uint32_t kDefaultPaymentInterval
static constexpr std::uint32_t kMinPaymentInterval
bool finalizeInvariants(STTx const &tx, TER result, XRPAmount fee, ReadView const &view, beast::Journal const &j) override
Check transaction-specific post-conditions after all entries have been visited.
Definition LoanSet.cpp:704
static bool checkExtraFeatures(PreflightContext const &ctx)
Definition LoanSet.cpp:44
TER doApply() override
Definition LoanSet.cpp:403
void visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override
Inspect a single ledger entry modified by this transaction.
Definition LoanSet.cpp:698
static constexpr std::uint32_t kDefaultGracePeriod
Number is a floating point type that can represent a wide range of values.
Definition Number.h:351
A view into a ledger.
Definition ReadView.h:41
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
virtual LedgerHeader const & header() const =0
Returns information about the ledger.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:180
std::shared_ptr< STLedgerEntry const > const & const_ref
T::value_type at(TypedField< T > const &f) const
Get the value of a field.
Definition STObject.h:1078
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:464
STObject getFieldObject(SField const &field) const
Definition STObject.cpp:678
static constexpr SeqProxy rawSequence(std::uint32_t v)
Factory function to return a sequence-based SeqProxy.
Definition SeqProxy.h:62
static NotTEC checkSign(PreclaimContext const &ctx)
static bool validNumericMinimum(std::optional< T > value, T min=T{})
Minimum will usually be zero.
Definition Transactor.h:643
beast::Journal const j_
Definition Transactor.h:155
ApplyView & view()
Definition Transactor.h:175
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
AccountID const accountID_
Definition Transactor.h:157
XRPAmount preFeeBalance_
Definition Transactor.h:158
static bool validDataLength(std::optional< Slice > const &slice, std::size_t maxLength)
ApplyContext & ctx_
Definition Transactor.h:153
static bool validNumericRange(std::optional< T > value, T max, T min=T{})
Definition Transactor.h:624
T is_same_v
T make_shared(T... args)
T max(T... args)
constexpr Zero kZero
Definition Zero.h:30
std::optional< NotTEC > preflightCheckSimulateKeys(ApplyFlags flags, STObject const &sigObject, beast::Journal j)
Checks the special signing key state needed for simulation.
NotTEC preflightCheckSigningKey(STObject const &sigObject, beast::Journal j)
Checks the validity of the transactor signing key.
Keylet loan(uint256 const &loanBrokerID, SeqProxy const &loanSeq) noexcept
Definition Indexes.cpp:573
Keylet vault(AccountID const &owner, SeqProxy const &seq) noexcept
Definition Indexes.cpp:561
Keylet loanBroker(AccountID const &owner, SeqProxy const &seq) noexcept
Definition Indexes.cpp:567
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:198
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
TER checkDeepFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
@ terNO_ACCOUNT
Definition TER.h:213
constexpr FlagValue tfInnerBatchTxn
Definition TxFlags.h:44
void increaseOwnerCount(ApplyView &view, SLE::ref accountSle, SLE::ref sponsorSle, std::uint32_t count, beast::Journal j)
Increase owner-count fields when the caller supplies the sponsor.
bool loanOriginationExceedsVaultMaximum(SLE::const_ref vaultSle, Number const &vaultTotal, Number const &interestDue)
VaultPhase getVaultPhase(ReadView const &view, SLE::const_ref vault)
Returns the current lifecycle phase of a vault.
constexpr T tenthBipsOfValue(T value, TenthBips< TBips > bips)
Definition Protocol.h:138
int getAssetsTotalScale(SLE::const_ref vaultSle)
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
void adjustImpreciseNumber(NumberProxy value, Number const &adjustment, Asset const &asset, int vaultScale)
@ tefBAD_LEDGER
Definition TER.h:162
void adjustLoanBrokerOwnerCount(ApplyView &view, SLE::ref brokerSle, std::int32_t delta, beast::Journal j)
Adjust a LoanBroker's owner count.
Number minimumBrokerCover(Number const &debtTotal, TenthBips32 coverRateMinimum, SLE::const_ref vaultSle)
TER addEmptyHolding(ApplyViewContext ctx, AccountID const &accountID, XRPAmount priorBalance, MPTIssue const &mptIssue, beast::Journal journal)
TenthBips< std::uint32_t > TenthBips32
Definition Units.h:454
TenthBips< std::uint16_t > TenthBips16
Definition Units.h:453
bool isReserveSponsored(STTx const &tx)
Whether the transaction's reserve is sponsored (sfSponsor present + spfSponsorReserve set).
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
constexpr std::size_t kMaxDataPayloadLength
The maximum length of Data payload.
Definition Protocol.h:302
TER checkLoanGuards(Asset const &vaultAsset, Number const &principalRequested, bool expectInterest, std::uint32_t paymentTotal, LoanProperties const &properties, beast::Journal j)
TER canAddHolding(ReadView const &view, MPTIssue const &mptIssue)
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:607
TER dirLink(ApplyView &view, AccountID const &owner, SLE::pointer &object, SF_UINT64 const &node=sfOwnerNode)
Definition View.cpp:343
static std::uint32_t getStartDate(ReadView const &view)
Definition LoanSet.cpp:220
AccountingDeltas loanOriginationDeltas(SLE::const_ref vaultSle, Number const &principalRequested, Number const &interestDue)
@ temINVALID
Definition TER.h:98
@ temINVALID_FLAG
Definition TER.h:99
@ temBAD_SIGNER
Definition TER.h:103
TERSubset< CanCvtToTER > TER
Definition TER.h:647
TER requireAuth(ReadView const &view, MPTIssue const &mptIssue, AccountID const &account, AuthType authType=AuthType::Legacy, std::uint8_t depth=0)
Check if the account lacks required authorization for MPT.
@ tecNO_ENTRY
Definition TER.h:309
@ tecINTERNAL
Definition TER.h:313
@ tecTOO_SOON
Definition TER.h:321
@ tecINSUFFICIENT_FUNDS
Definition TER.h:328
@ tecEXPIRED
Definition TER.h:317
@ tecPRECISION_LOSS
Definition TER.h:366
@ tecINSUFFICIENT_RESERVE
Definition TER.h:310
@ tecKILLED
Definition TER.h:319
@ tecMAX_SEQUENCE_REACHED
Definition TER.h:323
@ tecLIMIT_EXCEEDED
Definition TER.h:364
@ tecNO_PERMISSION
Definition TER.h:308
@ tecDUPLICATE
Definition TER.h:318
void associateAsset(STLedgerEntry &sle, Asset const &asset)
Associate an Asset with all sMD_NeedsAsset fields in a ledger entry.
TER accountSendMulti(ApplyView &view, AccountID const &senderID, Asset const &asset, MultiplePaymentDestinations const &receivers, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Like accountSend, except one account is sending multiple payments (with the same asset!...
LoanProperties computeLoanProperties(Rules const &rules, Asset const &asset, Number const &principalOutstanding, TenthBips32 interestRate, std::uint32_t paymentInterval, std::uint32_t paymentsRemaining, TenthBips32 managementFeeRate, std::int32_t minimumScale)
LoanState constructLoanState(Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding)
BaseUInt< 256 > uint256
Definition base_uint.h:580
XRPAmount accountReserve(ReadView const &view, SLE::const_ref sle, beast::Journal j, Adjustment adj={})
Returns the account reserve, in drops.
@ tesSUCCESS
Definition TER.h:245
bool checkLendingProtocolDependencies(Rules const &rules, STTx const &tx)
bool isRounded(Asset const &asset, Number const &value, std::int32_t scale)
NetClock::time_point closeTime
This structure captures the parts of a loan state.
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:83
ReadView const & view
Definition Transactor.h:86
beast::Journal const j
Definition Transactor.h:91
std::optional< uint256 const > const parentBatchId
Definition Transactor.h:90
State information when preflighting a tx.
Definition Transactor.h:38
beast::Journal const j
Definition Transactor.h:45
std::optional< uint256 const > parentBatchId
Definition Transactor.h:44
T time_since_epoch(T... args)