1#include <xrpl/tx/transactors/lending/LoanSet.h>
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/ApplyView.h>
9#include <xrpl/ledger/View.h>
10#include <xrpl/ledger/helpers/AccountRootHelpers.h>
11#include <xrpl/ledger/helpers/LendingHelpers.h>
12#include <xrpl/ledger/helpers/SponsorHelpers.h>
13#include <xrpl/ledger/helpers/TokenHelpers.h>
14#include <xrpl/ledger/helpers/VaultHelpers.h>
15#include <xrpl/protocol/AccountID.h>
16#include <xrpl/protocol/Asset.h>
17#include <xrpl/protocol/Feature.h>
18#include <xrpl/protocol/Indexes.h>
19#include <xrpl/protocol/LedgerFormats.h>
20#include <xrpl/protocol/Protocol.h>
21#include <xrpl/protocol/SField.h>
22#include <xrpl/protocol/STLedgerEntry.h>
23#include <xrpl/protocol/STNumber.h>
24#include <xrpl/protocol/STObject.h>
25#include <xrpl/protocol/STTakesAsset.h>
26#include <xrpl/protocol/STTx.h>
27#include <xrpl/protocol/SeqProxy.h>
28#include <xrpl/protocol/TER.h>
29#include <xrpl/protocol/TxFlags.h>
30#include <xrpl/protocol/Units.h>
31#include <xrpl/protocol/XRPAmount.h>
32#include <xrpl/tx/Transactor.h>
67 auto const& tx = ctx.
tx;
71 JLOG(ctx.
j.
debug()) <<
"LoanSet: reserve sponsorship is not allowed.";
77 !tx.isFieldPresent(sfCounterparty))
80 JLOG(ctx.
j.
debug()) <<
"BatchTrace[" << parentBatchId <<
"]: "
81 <<
"no Counterparty for inner LoanSet transaction.";
87 if (tx.isFieldPresent(sfCounterpartySignature))
88 return tx.getFieldObject(sfCounterpartySignature);
96 JLOG(ctx.
j.
warn()) <<
"LoanSet transaction must have a CounterpartySignature.";
106 if (
auto const data = tx[~sfData];
109 for (
auto const& field : {&sfLoanServiceFee, &sfLatePaymentFee, &sfClosePaymentFee})
115 auto const p = tx[sfPrincipalRequested];
128 if (!
validNumericRange(tx[~sfOverpaymentInterestRate], kMaxOverpaymentInterestRate))
131 if (
auto const paymentTotal = tx[~sfPaymentTotal]; paymentTotal && *paymentTotal <= 0)
134 auto const paymentInterval = tx[~sfPaymentInterval];
153 if (
auto const brokerID = ctx.
tx[~sfLoanBrokerID]; brokerID && *brokerID ==
beast::kZero)
169 if (
auto const c = ctx.
tx.
at(~sfCounterparty))
173 return broker->at(sfOwner);
198 auto const counterSig = tx.
getFieldObject(sfCounterpartySignature);
203 std::size_t const signerCount = [&counterSig]() ->
int {
206 if (counterSig.isFieldPresent(sfSigners))
207 return counterSig.getFieldArray(sfSigners).size();
208 return counterSig.isFieldPresent(sfTxnSignature) ? 1 : 0;
211 return normalCost + (signerCount * baseFee);
218 ~sfPrincipalRequested,
219 ~sfLoanOriginationFee,
238 auto const& tx = ctx.
tx;
248 using TimeType =
decltype(sfNextPaymentDueDate)::type::value_type;
251 static_assert(kMaxTime == 4'294'967'295);
258 if (grace > timeAvailable)
260 JLOG(ctx.
j.
warn()) <<
"Grace period exceeds protocol time limit.";
264 if (interval > timeAvailable)
266 JLOG(ctx.
j.
warn()) <<
"Payment interval exceeds protocol time limit.";
270 if (total > timeAvailable)
272 JLOG(ctx.
j.
warn()) <<
"Payment total exceeds protocol time limit.";
276 auto const timeLastPayment = timeAvailable - grace;
278 if (timeLastPayment / interval < total)
280 JLOG(ctx.
j.
warn()) <<
"Last payment due date, or grace period for "
281 "last payment exceeds protocol time limit.";
286 auto const account = tx[sfAccount];
287 auto const brokerID = tx[sfLoanBrokerID];
294 JLOG(ctx.
j.
warn()) <<
"LoanBroker does not exist.";
297 auto const brokerOwner = brokerSle->at(sfOwner);
298 auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
299 if (account != brokerOwner && counterparty != brokerOwner)
301 JLOG(ctx.
j.
warn()) <<
"Neither Account nor Counterparty are the owner "
302 "of the LoanBroker.";
305 auto const brokerPseudo = brokerSle->at(sfAccount);
307 auto const borrower = counterparty == brokerOwner ? account : counterparty;
312 JLOG(ctx.
j.
warn()) <<
"Borrower does not exist.";
328 JLOG(ctx.
j.
warn()) <<
"Vault is still in the subscription phase.";
333 JLOG(ctx.
j.
warn()) <<
"Vault has entered the redemption phase.";
338 auto const finalPayment =
344 <<
" seconds before the vault's redemption date.";
355 vault->at(sfAssetsTotal) >= vault->at(sfAssetsMaximum))
357 JLOG(ctx.
j.
warn()) <<
"Vault at maximum assets limit. Can't add another loan.";
361 Asset const asset = vault->at(sfAsset);
363 auto const vaultPseudo = vault->at(sfAccount);
371 if (
auto const value = tx[field]; value &&
STAmount{asset, *value} != *value)
373 JLOG(ctx.
j.
warn()) << field.f->getName() <<
" (" << *value
374 <<
") can not be represented as a(n) " <<
to_string(asset) <<
".";
390 auto const originationFee = tx[~sfLoanOriginationFee].value_or(
Number{});
401 JLOG(ctx.
j.
warn()) <<
"Vault pseudo-account is frozen.";
410 JLOG(ctx.
j.
warn()) <<
"Broker pseudo-account is frozen.";
420 JLOG(ctx.
j.
warn()) <<
"Borrower account is frozen.";
427 JLOG(ctx.
j.
warn()) <<
"Broker owner account is frozen.";
437 auto const& tx =
ctx_.tx;
440 auto const brokerID = tx[sfLoanBrokerID];
445 auto const brokerOwner = brokerSle->at(sfOwner);
453 auto const vaultPseudo = vaultSle->at(sfAccount);
454 Asset const vaultAsset = vaultSle->at(sfAsset);
456 auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
457 auto const borrower = counterparty == brokerOwner ?
accountID_ : counterparty;
464 auto const brokerPseudo = brokerSle->at(sfAccount);
466 if (!brokerPseudoSle)
470 auto const principalRequested = tx[sfPrincipalRequested];
472 auto vaultAvailableProxy = vaultSle->at(sfAssetsAvailable);
473 auto vaultTotalProxy = vaultSle->at(sfAssetsTotal);
475 if (vaultAvailableProxy < principalRequested)
477 JLOG(
j_.warn()) <<
"Insufficient assets available in the Vault to fund the loan.";
481 TenthBips32 const interestRate{tx[~sfInterestRate].value_or(0)};
497 properties.loanState.valueOutstanding,
499 properties.loanState.managementFeeDue);
502 *vaultSle->at(sfAssetsMaximum) == 0 ||
504 *vaultSle->at(sfAssetsMaximum) > *vaultTotalProxy,
505 "xrpl::LoanSet::doApply",
506 "instant-recognition vault is below maximum limit");
510 JLOG(
j_.warn()) <<
"Loan would exceed the maximum assets of the vault";
517 if (
auto const value = tx[field];
518 value && !
isRounded(vaultAsset, *value, properties.loanScale))
520 JLOG(
j_.warn()) << field.f->getName() <<
" (" << *value
521 <<
") has too much precision. Total loan value is "
522 << properties.loanState.valueOutstanding <<
" with a scale of "
523 << properties.loanScale;
538 if (properties.loanState.managementFeeDue < 0 || properties.loanState.valueOutstanding <= 0 ||
539 properties.periodicPayment <= 0)
542 JLOG(
j_.warn()) <<
"Computed loan properties are invalid. Does not compute."
543 <<
" Management fee: " << properties.loanState.managementFeeDue
544 <<
". Total Value: " << properties.loanState.valueOutstanding
545 <<
". PeriodicPayment: " << properties.periodicPayment;
550 auto const originationFee = tx[~sfLoanOriginationFee].value_or(
Number{});
552 auto const loanAssetsToBorrower = principalRequested - originationFee;
554 auto const [assetsTotalDelta, debtTotalDelta] =
556 auto const newDebtTotal = brokerSle->at(sfDebtTotal) + debtTotalDelta;
557 if (
auto const debtMaximum = brokerSle->at(sfDebtMaximum);
558 debtMaximum != 0 && debtMaximum < newDebtTotal)
560 JLOG(
j_.warn()) <<
"Loan would exceed the maximum debt limit of the LoanBroker.";
563 TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)};
565 auto const minCover = [&]() {
566 if (
ctx_.view().rules().enabled(fixCleanup3_2_0))
577 if (brokerSle->at(sfCoverAvailable) < minCover)
579 JLOG(
j_.warn()) <<
"Insufficient first-loss capital to cover the loan.";
600 borrower ==
accountID_ || borrower == counterparty,
601 "xrpl::LoanSet::doApply",
602 "borrower signed transaction");
603 auto applyViewContext =
ctx_.getApplyViewContext();
605 applyViewContext, borrower, borrowerSle->at(sfBalance).value().xrp(), vaultAsset,
j_);
623 brokerOwner ==
accountID_ || brokerOwner == counterparty,
624 "xrpl::LoanSet::doApply",
625 "broker owner signed transaction");
630 brokerOwnerSle->at(sfBalance).value().xrp(),
648 {{borrower, loanAssetsToBorrower}, {brokerOwner, originationFee}},
655 auto loanSequenceProxy = brokerSle->at(sfLoanSequence);
662 auto setLoanField = [&loan, &tx](
auto const& field,
std::uint32_t const defValue = 0) {
665 loan->at(field) = tx[field].value_or(defValue);
669 loan->at(sfLoanScale) = properties.loanScale;
670 loan->at(sfStartDate) = startDate;
671 loan->at(sfPaymentInterval) = paymentInterval;
672 loan->at(sfLoanSequence) = *loanSequenceProxy;
673 loan->at(sfLoanBrokerID) = brokerID;
674 loan->at(sfBorrower) = borrower;
676 if (tx.isFlag(tfLoanOverpayment))
677 loan->setFlag(lsfLoanOverpayment);
678 setLoanField(~sfLoanOriginationFee);
679 setLoanField(~sfLoanServiceFee);
680 setLoanField(~sfLatePaymentFee);
681 setLoanField(~sfClosePaymentFee);
682 setLoanField(~sfOverpaymentFee);
683 setLoanField(~sfInterestRate);
684 setLoanField(~sfLateInterestRate);
685 setLoanField(~sfCloseInterestRate);
686 setLoanField(~sfOverpaymentInterestRate);
689 loan->at(sfPrincipalOutstanding) = principalRequested;
690 loan->at(sfPeriodicPayment) = properties.periodicPayment;
691 loan->at(sfTotalValueOutstanding) = properties.loanState.valueOutstanding;
692 loan->at(sfManagementFeeOutstanding) = properties.loanState.managementFeeDue;
693 loan->at(sfPreviousPaymentDueDate) = 0;
694 loan->at(sfNextPaymentDueDate) = startDate + paymentInterval;
695 loan->at(sfPaymentRemaining) = paymentTotal;
699 vaultAvailableProxy -= principalRequested;
700 vaultTotalProxy += assetsTotalDelta;
702 *vaultAvailableProxy <= *vaultTotalProxy,
703 "xrpl::LoanSet::doApply",
704 "assets available must not be greater than assets outstanding");
705 view.update(vaultSle);
710 loanSequenceProxy += 1;
713 if (loanSequenceProxy == 0)
715 view.update(brokerSle);
718 if (
auto const ter =
dirLink(
view, brokerPseudo, loan, sfLoanBrokerNode))
721 if (
auto const ter =
dirLink(
view, borrower, loan, sfOwnerNode))
A generic endpoint for log messages.
void visitInvariantEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after) override
Inspect a single ledger entry modified by this transaction.
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
static constexpr std::uint32_t kDefaultPaymentTotal
static std::vector< OptionaledField< STNumber > > const & getValueFields()
static NotTEC preflight(PreflightContext const &ctx)
static NotTEC checkSign(PreclaimContext const &ctx)
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.
static bool checkExtraFeatures(PreflightContext const &ctx)
static constexpr std::uint32_t kDefaultGracePeriod
Number is a floating point type that can represent a wide range of values.
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.
std::shared_ptr< STLedgerEntry const > const & ConstRef
T::value_type at(TypedField< T > const &f) const
Get the value of a field.
bool isFieldPresent(SField const &field) const
STObject getFieldObject(SField const &field) const
static constexpr SeqProxy rawSequence(std::uint32_t v)
Factory function to return a sequence-based SeqProxy.
static NotTEC checkSign(PreclaimContext const &ctx)
static bool validNumericMinimum(std::optional< T > value, T min=T{})
Minimum will usually be zero.
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
AccountID const accountID_
static bool validDataLength(std::optional< Slice > const &slice, std::size_t maxLength)
static bool validNumericRange(std::optional< T > value, T max, T min=T{})
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 vault(AccountID const &owner, SeqProxy const &seq) noexcept
Keylet loanBroker(AccountID const &owner, SeqProxy const &seq) noexcept
Keylet account(AccountID const &id) noexcept
AccountID root.
Keylet loan(UInt256 const &loanBrokerID, SeqProxy const &loanSeq) noexcept
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
TER checkDeepFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
constexpr FlagValue tfInnerBatchTxn
bool loanOriginationExceedsVaultMaximum(SLE::ConstRef vaultSle, Number const &vaultTotal, Number const &interestDue)
constexpr T tenthBipsOfValue(T value, TenthBips< TBips > bips)
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
void adjustImpreciseNumber(NumberProxy value, Number const &adjustment, Asset const &asset, int vaultScale)
int getAssetsTotalScale(SLE::ConstRef vaultSle)
TER addEmptyHolding(ApplyViewContext ctx, AccountID const &accountID, XRPAmount priorBalance, MPTIssue const &mptIssue, beast::Journal journal)
TenthBips< std::uint32_t > TenthBips32
TenthBips< std::uint16_t > TenthBips16
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 isReserveSponsored(STTx const &tx)
Whether the transaction's reserve is sponsored (sfSponsor present + spfSponsorReserve set).
std::string to_string(BaseUInt< Bits, Tag > const &a)
constexpr std::size_t kMaxDataPayloadLength
The maximum length of Data payload.
TER checkLoanGuards(Asset const &vaultAsset, Number const &principalRequested, bool expectInterest, std::uint32_t paymentTotal, LoanProperties const &properties, beast::Journal j)
Number minimumBrokerCover(Number const &debtTotal, TenthBips32 coverRateMinimum, SLE::ConstRef vaultSle)
TER canAddHolding(ReadView const &view, MPTIssue const &mptIssue)
TERSubset< CanCvtToNotTEC > NotTEC
TER dirLink(ApplyView &view, AccountID const &owner, SLE::pointer &object, SF_UINT64 const &node=sfOwnerNode)
void adjustLoanBrokerOwnerCount(ApplyView &view, SLE::Ref brokerSle, std::int32_t delta, beast::Journal j)
Adjust a LoanBroker's owner count.
static std::uint32_t getStartDate(ReadView const &view)
VaultPhase getVaultPhase(ReadView const &view, SLE::ConstRef vault)
Returns the current lifecycle phase of a vault.
TERSubset< CanCvtToTER > TER
XRPAmount accountReserve(ReadView const &view, SLE::ConstRef sle, beast::Journal j, Adjustment adj={})
Returns the account reserve, in drops.
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.
@ tecINSUFFICIENT_RESERVE
@ tecMAX_SEQUENCE_REACHED
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)
constexpr std::uint32_t kLoanRedemptionBuffer
Minimum gap between a closed-ended loan's final scheduled payment and the vault's RedemptionDate.
VaultVersion getVaultVersion(SLE::ConstRef vault)
Resolves a Vault's LEVersion, the single point every accounting touch point should call to determine ...
AccountingDeltas loanOriginationDeltas(SLE::ConstRef vaultSle, Number const &principalRequested, Number const &interestDue)
LoanState constructLoanState(Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding)
constexpr std::uint32_t kMinInvestmentPeriod
Bounds on the length of a closed-ended vault's Investment phase (RedemptionDate - SubscriptionDate).
bool holdingExists(ReadView const &view, AccountID const &account, Asset const &asset)
True if the account already holds this asset (or is the issuer / XRP).
bool checkLendingProtocolDependencies(Rules const &rules, STTx const &tx)
bool isRounded(Asset const &asset, Number const &value, std::int32_t scale)
This structure captures the parts of a loan state.
State information when determining if a tx is likely to claim a fee.
std::optional< UInt256 const > const parentBatchId
State information when preflighting a tx.
std::optional< UInt256 const > parentBatchId
T time_since_epoch(T... args)