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/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>
60 auto const& tx = ctx.
tx;
64 JLOG(ctx.
j.
debug()) <<
"LoanSet: reserve sponsorship is not allowed.";
70 !tx.isFieldPresent(sfCounterparty))
73 JLOG(ctx.
j.
debug()) <<
"BatchTrace[" << parentBatchId <<
"]: "
74 <<
"no Counterparty for inner LoanSet transaction.";
80 if (tx.isFieldPresent(sfCounterpartySignature))
81 return tx.getFieldObject(sfCounterpartySignature);
86 JLOG(ctx.
j.
warn()) <<
"LoanSet transaction must have a CounterpartySignature.";
96 if (
auto const data = tx[~sfData];
99 for (
auto const& field : {&sfLoanServiceFee, &sfLatePaymentFee, &sfClosePaymentFee})
105 auto const p = tx[sfPrincipalRequested];
118 if (!
validNumericRange(tx[~sfOverpaymentInterestRate], kMaxOverpaymentInterestRate))
121 if (
auto const paymentTotal = tx[~sfPaymentTotal]; paymentTotal && *paymentTotal <= 0)
124 auto const paymentInterval = tx[~sfPaymentInterval];
143 if (
auto const brokerID = ctx.
tx[~sfLoanBrokerID]; brokerID && *brokerID ==
beast::kZero)
159 if (
auto const c = ctx.
tx.
at(~sfCounterparty))
163 return broker->at(sfOwner);
188 auto const counterSig = tx.
getFieldObject(sfCounterpartySignature);
193 std::size_t const signerCount = [&counterSig]() ->
int {
196 if (counterSig.isFieldPresent(sfSigners))
197 return counterSig.getFieldArray(sfSigners).size();
198 return counterSig.isFieldPresent(sfTxnSignature) ? 1 : 0;
201 return normalCost + (signerCount * baseFee);
208 ~sfPrincipalRequested,
209 ~sfLoanOriginationFee,
228 auto const& tx = ctx.
tx;
238 using timeType =
decltype(sfNextPaymentDueDate)::type::value_type;
241 static_assert(kMaxTime == 4'294'967'295);
248 if (grace > timeAvailable)
250 JLOG(ctx.
j.
warn()) <<
"Grace period exceeds protocol time limit.";
254 if (interval > timeAvailable)
256 JLOG(ctx.
j.
warn()) <<
"Payment interval exceeds protocol time limit.";
260 if (total > timeAvailable)
262 JLOG(ctx.
j.
warn()) <<
"Payment total exceeds protocol time limit.";
266 auto const timeLastPayment = timeAvailable - grace;
268 if (timeLastPayment / interval < total)
270 JLOG(ctx.
j.
warn()) <<
"Last payment due date, or grace period for "
271 "last payment exceeds protocol time limit.";
276 auto const account = tx[sfAccount];
277 auto const brokerID = tx[sfLoanBrokerID];
284 JLOG(ctx.
j.
warn()) <<
"LoanBroker does not exist.";
287 auto const brokerOwner = brokerSle->at(sfOwner);
288 auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
289 if (account != brokerOwner && counterparty != brokerOwner)
291 JLOG(ctx.
j.
warn()) <<
"Neither Account nor Counterparty are the owner "
292 "of the LoanBroker.";
295 auto const brokerPseudo = brokerSle->at(sfAccount);
297 auto const borrower = counterparty == brokerOwner ? account : counterparty;
302 JLOG(ctx.
j.
warn()) <<
"Borrower does not exist.";
318 JLOG(ctx.
j.
warn()) <<
"Vault is still in the subscription phase.";
323 JLOG(ctx.
j.
warn()) <<
"Vault has entered the redemption phase.";
328 auto const finalPayment =
330 if (finalPayment >= vault->at(sfRedemptionDate))
332 JLOG(ctx.
j.
warn()) <<
"Final loan payment date is on or after "
333 "the vault's redemption date.";
339 if (vault->at(sfAssetsMaximum) != 0 && vault->at(sfAssetsTotal) >= vault->at(sfAssetsMaximum))
341 JLOG(ctx.
j.
warn()) <<
"Vault at maximum assets limit. Can't add another loan.";
345 Asset const asset = vault->at(sfAsset);
347 auto const vaultPseudo = vault->at(sfAccount);
355 if (
auto const value = tx[field]; value &&
STAmount{asset, *value} != *value)
357 JLOG(ctx.
j.
warn()) << field.f->getName() <<
" (" << *value
358 <<
") can not be represented as a(n) " <<
to_string(asset) <<
".";
369 JLOG(ctx.
j.
warn()) <<
"Vault pseudo-account is frozen.";
378 JLOG(ctx.
j.
warn()) <<
"Broker pseudo-account is frozen.";
388 JLOG(ctx.
j.
warn()) <<
"Borrower account is frozen.";
395 JLOG(ctx.
j.
warn()) <<
"Broker owner account is frozen.";
405 auto const& tx =
ctx_.tx;
408 auto const brokerID = tx[sfLoanBrokerID];
413 auto const brokerOwner = brokerSle->at(sfOwner);
418 auto const vaultSle =
view.peek(keylet ::vault(brokerSle->at(sfVaultID)));
421 auto const vaultPseudo = vaultSle->at(sfAccount);
422 Asset const vaultAsset = vaultSle->at(sfAsset);
424 auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
425 auto const borrower = counterparty == brokerOwner ?
accountID_ : counterparty;
432 auto const brokerPseudo = brokerSle->at(sfAccount);
434 if (!brokerPseudoSle)
438 auto const principalRequested = tx[sfPrincipalRequested];
440 auto vaultAvailableProxy = vaultSle->at(sfAssetsAvailable);
441 auto vaultTotalProxy = vaultSle->at(sfAssetsTotal);
443 if (vaultAvailableProxy < principalRequested)
445 JLOG(
j_.warn()) <<
"Insufficient assets available in the Vault to fund the loan.";
449 TenthBips32 const interestRate{tx[~sfInterestRate].value_or(0)};
465 properties.loanState.valueOutstanding,
467 properties.loanState.managementFeeDue);
470 *vaultSle->at(sfAssetsMaximum) == 0 || *vaultSle->at(sfAssetsMaximum) > *vaultTotalProxy,
471 "xrpl::LoanSet::doApply",
472 "Vault is below maximum limit");
476 JLOG(
j_.warn()) <<
"Loan would exceed the maximum assets of the vault";
483 if (
auto const value = tx[field];
484 value && !
isRounded(vaultAsset, *value, properties.loanScale))
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;
504 if (properties.loanState.managementFeeDue < 0 || properties.loanState.valueOutstanding <= 0 ||
505 properties.periodicPayment <= 0)
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;
516 auto const originationFee = tx[~sfLoanOriginationFee].value_or(
Number{});
518 auto const loanAssetsToBorrower = principalRequested - originationFee;
520 auto const [assetsTotalDelta, debtTotalDelta] =
522 auto const newDebtTotal = brokerSle->at(sfDebtTotal) + debtTotalDelta;
523 if (
auto const debtMaximum = brokerSle->at(sfDebtMaximum);
524 debtMaximum != 0 && debtMaximum < newDebtTotal)
526 JLOG(
j_.warn()) <<
"Loan would exceed the maximum debt limit of the LoanBroker.";
529 TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)};
531 auto const minCover = [&]() {
532 if (
ctx_.view().rules().enabled(fixCleanup3_2_0))
543 if (brokerSle->at(sfCoverAvailable) < minCover)
545 JLOG(
j_.warn()) <<
"Insufficient first-loss capital to cover the loan.";
566 borrower ==
accountID_ || borrower == counterparty,
567 "xrpl::LoanSet::doApply",
568 "borrower signed transaction");
569 auto applyViewContext =
ctx_.getApplyViewContext();
571 applyViewContext, borrower, borrowerSle->at(sfBalance).value().xrp(), vaultAsset,
j_);
589 brokerOwner ==
accountID_ || brokerOwner == counterparty,
590 "xrpl::LoanSet::doApply",
591 "broker owner signed transaction");
596 brokerOwnerSle->at(sfBalance).value().xrp(),
614 {{borrower, loanAssetsToBorrower}, {brokerOwner, originationFee}},
621 auto loanSequenceProxy = brokerSle->at(sfLoanSequence);
628 auto setLoanField = [&loan, &tx](
auto const& field,
std::uint32_t const defValue = 0) {
631 loan->at(field) = tx[field].value_or(defValue);
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;
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);
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;
665 vaultAvailableProxy -= principalRequested;
666 vaultTotalProxy += assetsTotalDelta;
668 *vaultAvailableProxy <= *vaultTotalProxy,
669 "xrpl::LoanSet::doApply",
670 "assets available must not be greater than assets outstanding");
671 view.update(vaultSle);
676 loanSequenceProxy += 1;
679 if (loanSequenceProxy == 0)
681 view.update(brokerSle);
684 if (
auto const ter =
dirLink(
view, brokerPseudo, loan, sfLoanBrokerNode))
687 if (
auto const ter =
dirLink(
view, borrower, loan, sfOwnerNode))
A generic endpoint for log messages.
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)
void visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override
Inspect a single ledger entry modified by this transaction.
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 & const_ref
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 loan(uint256 const &loanBrokerID, SeqProxy const &loanSeq) noexcept
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.
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
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)
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)
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
TenthBips< std::uint16_t > TenthBips16
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)
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)
static std::uint32_t getStartDate(ReadView const &view)
AccountingDeltas loanOriginationDeltas(SLE::const_ref vaultSle, Number const &principalRequested, Number const &interestDue)
TERSubset< CanCvtToTER > TER
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)
LoanState constructLoanState(Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding)
XRPAmount accountReserve(ReadView const &view, SLE::const_ref sle, beast::Journal j, Adjustment adj={})
Returns the account reserve, in drops.
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)